Linux知识点 -- 进程间通信(二)

news2024/11/24 8:32:16

Linux知识点 – 进程间通信(二)

文章目录

  • Linux知识点 -- 进程间通信(二)
  • 一、System V共享内存
    • 1.原理
    • 2.申请共享内存
    • 3.System V共享内存的使用
    • 4.为共享内存添加访问控制
  • 二、信号量(概念理解)
    • 1.概念
    • 2.信号量


一、System V共享内存

1.原理

在这里插入图片描述
先在内存中申请空间,然后将这段空间映射到不同进程的地址空间中,这就叫做共享内存;
一般都是映射在进程的堆栈之间的共享区;
共享内存不属于任何一个进程,它属于操作系统;
操作系统对共享内存的管理,是先描述再组织,先通过内核数据结构描述共享内存的属性信息,再将它们组织起来;
共享内存 = 共享内存块 + 对应的共享内存的内核数据结构;
共享区属于用户空间,不用经过系统调用,直接可以访问;
双方进程如果要通信,直接进行内存级的读写即可;
之前的管道是一种文件。是OS中的一种数据结构,所以用户无权直接访问,需要进行系统调用;

2.申请共享内存

在这里插入图片描述
shmget接口能够申请共享内存;

  • 参数:
    key:通信双方的进程,通过key值来保证是通信的双方来创建的共享内存,相当于一个验证值,需要在系统内是唯一的,通信双方使用同一个key;
    size:内存大小,一般是页(4byte)的整数倍;
    shmflag:有两个选项:IPC_CREAT和IPC_EXCL;
    IPC_CREAT能单独出现,代表如果共享内存已存在,则获取之;如果不存在,就创建之,并返回;
    IPC_EXCL必须和IPC_CREAT组合使用,代表如果共享内存不存在,就创建之,并返回;如果已存在,出错并返回;
    0就代表IPC_CREAT;

    返回值:成功会返回共享内存id,失败返回-1;

ftok函数:生成唯一的key
在这里插入图片描述

  • 参数:
    ==pathname:==文件路径,一定要保证用户有权限;
    ==id:==项目id,随便给,一般是0 - 255;
    返回值:成功,返回key值;失败,返回-1;
    ftok会拿路径文件的inode,和id形成一个唯一的key,生成结果是有可能重复的;

3.System V共享内存的使用

  • Makefile:
.PHONY:all
all:shmClient shmServer

shmServer:shmServer.cc
	g++ -o $@ $^ -std=c++11 
shmClient:shmClient.cc
	g++ -o $@ $^ -std=c++11 

.PHONY:clean
claen:
	rm -f shmServer shmClient
  • Log.hpp
#ifndef _LOG_H_
#define _LOG_H_

#include<iostream>
#include<ctime>

#define DeBug   0
#define Notice  1
#define Waring  2
#define Error   3

const std::string msg[] = {
    "DeBug",
    "Notice",
    "Waring",
    "Error"
};

std::ostream &Log(std::string message, int level)
{
    std::cout << " | " << (unsigned)time(nullptr) << " | " << msg[level] << " | " << message;
    return std::cout;
}
#endif
  • comm.hpp
#ifndef _COMM_H_
#define _COMM_H_

#include<iostream>
#include<cstdio>
#include<unistd.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<cassert>
#include "Log.hpp"

using namespace std;

#define PATH_NAME "/usr/lmx" //路径,一定保证有权限
#define PROJ_ID 0X66   
#define SHM_SIZE 4096 //共享内存大小,最好是页(4byte)的整数倍

#endif
  • shmServer.cc
    #include “comm.hpp”

string TransToHex(key_t k)
{
char buffer[32];
snprintf(buffer, sizeof(buffer), “0x%x”, k);
return buffer;
}

int main()
{
// 1.创建公共的key值
key_t key = ftok(PATH_NAME, PROJ_ID);
if (key == -1)
{
perror(“ftok”);
exit(1);
}

Log("creat key done", DeBug) << "server key : " << TransToHex(key) << endl;

// 2.创建共享内存 -- 建议创建一个全新的共享内存 -- 通信的发起者
int shmid = shmget(key, SHM_SIZE, IPC_CREAT | IPC_EXCL | 0666);
if (shmid == -1)
{
    perror("shmget");
    exit(2);
}
Log("shm creat done", DeBug) << "shmid : " << shmid << endl;

//3.将指定的共享内存,挂接到自己的地址空间
char* shmaddr = (char*)shmat(shmid, nullptr, 0);
Log("attach shm done", DeBug) << "shmid : " << shmid << endl;

//这里就是通信逻辑了
//将共享内存看作一个大字符串
//shmaddr就是这个字符串的起始地址
for(;;)
{
    printf("%s\n", shmaddr);//不断打印这个字符串的内容
    if(strcmp(shmaddr, "quit") == 0)
    {
        break;
    }
    sleep(1);
}

//4.将指定的共享内存,从自己的地址空间中去关联
int n = shmdt(shmaddr);
if(n == -1)
{
    perror("shmdt");
    exit(3);
}
Log("detach shm done", DeBug) << "shmid : " << shmid << endl;

//5.删除共享内存,IPC_RMID即便是有进程和当下的shm挂接,依旧删除共享内存
n = shmctl(shmid, IPC_RMID, nullptr);
if(n == -1)
{
    perror("shmctl");
    exit(4);
}
Log("delete shm done", DeBug) << "shmid : " << shmid << endl;

return 0;

}


注意:
(1)
在这里插入图片描述
要保证创建出唯一的key;*
(2)
在这里插入图片描述
创建全新的共享内存,0666代表共享内存的权限;
共享内存的大小最好是页的整数倍,否则会造成空间浪费,多开空间,但是没有权限访问;
在这里插入图片描述
第二次创建的时候,提示共享内存已存在;
在这里插入图片描述
(3)ipcs -m:查看共享内存信息;
在这里插入图片描述
ipcrm -m shmid:删除共享内存(不能用key删除)
共享内存的生命周期随内核;
与文件不一样,文件的生命周期,如果进程退出,没有其他进程再关联这个文件,那么就会被回收;

在这里插入图片描述
在这里插入图片描述
perms属性就是共享内存的权限,

(4)因此,当进程结束后,共享内存还存在,我们继续要删除它,使用系统接口:
shmctl:删除共享内存
在这里插入图片描述
在这里插入图片描述
(5)nattch属性是挂接的共享内存个数,共享内存创建好之后,需要挂接在自己的进程地址空间;
shmat:挂接共享内存
在这里插入图片描述
参数:
shmid:共享内存id
shmaddr:挂接虚拟地址,直接设为0,让os挂接
shmflg:挂接方式
返回值:成功返回共享内存addr虚拟地址,失败返回-1

使用:
将返回值作为共享内存的起始地址;
在这里插入图片描述
shmdt:去关联
在这里插入图片描述
参数:
shmaddr:共享内存地址
返回值:成功返回0,失败返回-1

  • shmClient.cc
#include "comm.hpp"

int main()
{
    // 客户端也获取key
    key_t key = ftok(PATH_NAME, PROJ_ID);
    if (key < 0)
    {
        Log("creat key failed", Error) << "client key : " << key << endl;
        exit(1);
    }
    Log("creat key done", DeBug) << "client key : " << key << endl;

    // 获取共享内存
    int shmid = shmget(key, SHM_SIZE, 0);
    if (shmid == -1)
    {
        Log("creat shm failed", Error) << "client key : " << key << endl;
        exit(2);
    }
    Log("creat shm done", DeBug) << "client key : " << key << endl;


    // 挂接共享内存
    char *shmaddr = (char *)shmat(shmid, nullptr, 0);
    if (shmaddr == nullptr)
    {
        Log("attach shm failed", Error) << "client key : " << key << endl;
    }
    Log("attach shm done", DeBug) << "client key : " << key << endl;
    
    // 使用
    //client将共享内存看作一个char类型的buffer
    //客户端从键盘读取消息,直接读到共享内存中
    while (true)
    {
        ssize_t s = read(0, shmaddr, SHM_SIZE - 1);
        if(s > 0)
        {
            shmaddr[s - 1] = 0;
            if(strcmp(shmaddr, "quit") == 0)//读到quit,客户端退出
            {
                break;
            }
        }
    }
    
    // char a = 'a';
    // for(; a <= 'z'; a++)
    // {
    //     //每一次都向shmaddr(共享内存的起始地址)写入
    //     snprintf(shmaddr, SHM_SIZE - 1, 
    //             "hello server, 我是其他进程,我的pid: %d, inc: %c\n", 
    //             getpid(), a);
    //     sleep(2);
    // }

    // 去关联
    int n = shmdt(shmaddr);
    if (n == -1)
    {
        perror("shmdt");
        exit(3);
    }
    Log("detach shm done", DeBug) << "client key : " << key << endl;

    // client不需要删除shm

    return 0;
}

注意:
(1)共享内存的使用,直接将共享内存看作一个char类型的buffer,直接向里面写入数据
在这里插入图片描述
从stdin中键盘读取消息,直接读取到shmaddr这个地址,即共享内存的起始地址;

运行结果:
服务端:
在这里插入图片描述
客户端:
在这里插入图片描述

  • 注:
    (1)只要是通信双方使用shm,一方直接向共享内存中写入数据,另一方就可以立马看到对方写入的数据;共享内存是所有进程间通信中最快的,不需要过多的拷贝;
    (2)管道通信中,一次通信需要多次拷贝,用户从键盘输入数据到缓冲区是一次拷贝,从缓冲区向管道文件写入数据又是一次拷贝,从管道文件向缓冲区读取数据是一次拷贝,从缓冲区将数据打印又是一次拷贝;

    在这里插入图片描述

(3)共享内存只需要两次拷贝,从键盘输入的数据直接写入shm,这是一次拷贝,直接将shm的数据打印出来,这是第二次拷贝;
在这里插入图片描述

4.为共享内存添加访问控制

从上面的结果可以看出,即便是客户端还没有挂接共享内存,服务端就已经开始不停读取数据了,这就表明共享内存是不带访问控制的,会带来一定的并发问题;
然而,管道是自带访问控制的,我们可以利用管道通信来为共享内存添加访问控制;
comm.hpp

#ifndef _COMM_H_
#define _COMM_H_

#include <iostream>
#include <cstdio>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <cassert>
#include <cstring>
#include <sys/stat.h>
#include <fcntl.h>
#include "Log.hpp"

using namespace std;

#define PATH_NAME "/home/lmx" // 路径,一定保证有权限
#define PROJ_ID 0X66
#define SHM_SIZE 4096 // 共享内存大小,最好是页(4byte)的整数倍

#define FIFO_NAME "./fifo"

class Init
{
public:
    Init()
    {
        umask(0);
        int n = mkfifo(FIFO_NAME, 0666);
        assert(n == 0);
        (void)n;
        Log("creat fifo succsee", Notice) << "\n";
    }

    ~Init()
    {
        unlink(FIFO_NAME);
        Log("remove fifo succsee", Notice) << "\n";
    }
};


#define READ O_RDONLY
#define WRITE O_WRONLY

int OpenFIFO(std::string pathname, int flags)
{
    int fd = open(pathname.c_str(), flags);
    assert(fd >= 0);
    return fd;
}

void Wait(int fd)
{
    Log("waiting...", Notice) << "\n";
    uint32_t temp = 0;
    ssize_t s = read(fd, &temp, sizeof(uint32_t));
    assert(s == sizeof(uint32_t));
    (void)s;
}

void Signal(int fd)
{
    uint32_t temp = 1;
    ssize_t s = write(fd, &temp, sizeof(uint32_t));
    assert(s == sizeof(uint32_t));
    (void)s;
    Log("aweaking...", Notice) << "\n";
}

void CloseFIFO(int fd)
{
    close(fd);
}

#endif

注:
(1)创建了一个类,类的构造函数有创建管道文件,一旦类实例化出对象,调用构造函数,就能够创建一个管道文件,后面就是对管道文件的读写控制了;
在这里插入图片描述
shmServer.cc

#include "comm.hpp"

string TransToHex(key_t k)
{
    char buffer[32];
    snprintf(buffer, sizeof(buffer), "0x%x", k);
    return buffer;
}

int main()
{
    Init init;
    // 对应的程序在加载的时候,会自动构建全局变量,就要调用该类构造函数 -- 创建管道文件
    // 程序退出的时候,全局变量会被析构,会自动删除管道文件

    // 1.创建公共的key值
    key_t key = ftok(PATH_NAME, PROJ_ID);
    if (key == -1)
    {
        perror("ftok");
        exit(1);
    }

    Log("creat key done", DeBug) << "server key : " << TransToHex(key) << endl;

    // 2.创建共享内存 -- 建议创建一个全新的共享内存 -- 通信的发起者
    int shmid = shmget(key, SHM_SIZE, IPC_CREAT | IPC_EXCL | 0666);
    if (shmid == -1)
    {
        perror("shmget");
        exit(2);
    }
    Log("shm creat done", DeBug) << "shmid : " << shmid << endl;

    // 3.将指定的共享内存,挂接到自己的地址空间
    char *shmaddr = (char *)shmat(shmid, nullptr, 0);
    Log("attach shm done", DeBug) << "shmid : " << shmid << endl;

    // 这里就是通信逻辑了
    // 将共享内存看作一个大字符串
    // shmaddr就是这个字符串的起始地址

    //使用管道进行访问控制
    int fd = OpenFIFO(FIFO_NAME, READ);

    for (;;)
    {
        Wait(fd);//等待客户端响应,
                    //使用管道文件的访问控制,如果客户端没有向管道内写入数据,那么该进程会一直阻塞
        printf("%s\n", shmaddr); // 不断打印这个字符串的内容
        if (strcmp(shmaddr, "quit") == 0)
        {
            break;
        }
        sleep(1);
    }

    CloseFIFO(fd);

    // 4.将指定的共享内存,从自己的地址空间中去关联
    int n = shmdt(shmaddr);
    if (n == -1)
    {
        perror("shmdt");
        exit(3);
    }
    Log("detach shm done", DeBug) << "shmid : " << shmid << endl;

    // 5.删除共享内存,IPC_RMID即便是有进程和当下的shm挂接,依旧删除共享内存
    n = shmctl(shmid, IPC_RMID, nullptr);
    if (n == -1)
    {
        perror("shmctl");
        exit(4);
    }
    Log("delete shm done", DeBug) << "shmid : " << shmid << endl;

    return 0;
}

注:
(1)在服务端先创建一个管道文件
在这里插入图片描述
(2)在读取共享内存中的数据前,先读取管道数据,看客户端是否响应;
在这里插入图片描述
shmClient.cc

#include "comm.hpp"

int main()
{
    // 客户端也获取key
    key_t key = ftok(PATH_NAME, PROJ_ID);
    if (key < 0)
    {
        Log("creat key failed", Error) << "client key : " << key << endl;
        exit(1);
    }
    Log("creat key done", DeBug) << "client key : " << key << endl;

    // 获取共享内存
    int shmid = shmget(key, SHM_SIZE, 0);
    if (shmid == -1)
    {
        Log("creat shm failed", Error) << "client key : " << key << endl;
        exit(2);
    }
    Log("creat shm done", DeBug) << "client key : " << key << endl;


    // 挂接共享内存
    char *shmaddr = (char *)shmat(shmid, nullptr, 0);
    if (shmaddr == nullptr)
    {
        Log("attach shm failed", Error) << "client key : " << key << endl;
    }
    Log("attach shm done", DeBug) << "client key : " << key << endl;
    
    // 使用
    //client将共享内存看作一个char类型的buffer
    //客户端从键盘读取消息,直接读到共享内存中

    //使用管道进行访问控制
    int fd = OpenFIFO(FIFO_NAME, WRITE);

    while (true)
    {
        ssize_t s = read(0, shmaddr, SHM_SIZE - 1);
        if(s > 0)
        {
            shmaddr[s - 1] = 0;
            Signal(fd);//向管道写入数据
            if(strcmp(shmaddr, "quit") == 0)//读到quit,客户端退出
            {
                break;
            }
        }
    }

    CloseFIFO(fd);
    
    // 去关联
    int n = shmdt(shmaddr);
    if (n == -1)
    {
        perror("shmdt");
        exit(3);
    }
    Log("detach shm done", DeBug) << "client key : " << key << endl;

    // client不需要删除shm

    return 0;
}

注:
(1)在向共享内存写入数据前,先向管道写入信号,表明客户端准备写入数据,唤醒服务端:
在这里插入图片描述

运行结果:
当运行服务端,但是客户端未响应时,服务端会等待客户端响应,进程阻塞;
在这里插入图片描述
当客户端响应时,服务端会被唤醒,读取共享内存中的数据:
在这里插入图片描述
退出:
在这里插入图片描述

二、信号量(概念理解)

1.概念

  • 基于对共享内存的理解:
    为了让进程间通信,让不同的进程之间,看到同一份资源,我们之前讲的所有的进程间通信都是基于这种方式;
    而让不同的进程看到同一份资源,比如共享内存,也带来了一些时序问题,会造成数据的不一致

  • 概念
    (1)临界资源:多个进程(执行流)看到的公共的一份资源;
    (2)临界区:自己的进程,访问临界资源的代码;
    (3)互斥:为了更好的进行临界区的维护,可以让多执行流在任何时刻,都只能有一个进程进入临界区;
    (4)原子性:要么不做,要么做完,没有中间状态;

2.信号量

我们平常看电影前,会先买票,电影院中的座位就相当于资源,当你买了票,这个座位就真正属于你,买票的本质就是对座位的预定机制;
对于进程来说,访问临界资源中的一部分,不能让进程直接去使用临界资源,需要先申请信号量
信号量的本质是一个计数器

  • 申请信号量:
    (1)申请信号量的本质,就是让信号量技术器 - -;
    (2)申请信号量成功,临界资源内部,一定给进程预留了需要的资源,申请信号量的本质就是对临界资源的一种预定机制;

  • 释放信号量:
    释放信号量就是将计数器++;

如果将信号量计数器设为全局变量(整数n,存放在共享内存),让多个进程看到同一个全局变量,大家都能够进行信号量的申请,这样是不行的;
因为CPU在执行n++这个指令的时候,其实执行了三条语句:
(1)将内存中的数据加载到CPU内的寄存器(读指令);
(2)n–(执行指令);
(3)将CPU修改完毕的值再写入内存(写指令);

而执行流在执行的时候,在任何时刻都是能被切换的;
例如:
如果信号量刚开始是5,client在申请信号量的时候,第一步就被切换了,寄存器里的数据保存为上下文数据,
由server申请信号量,如果server将信号量减到2了,此时server被切换,client回来
client回来的时候就会将上下文数据恢复,将信号量恢复为5,再申请信号量,这时信号量就变为了4;

寄存器只有一套,被所有执行流共享,但是寄存器内的数据,属于每一个执行流,属于该执行流的上下文数据;
这样设计,会导致信号量是不安全的;
因此,申请和释放信号量这两个操作,必须是原子的
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/841691.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

GG修改美食大战老鼠宝石等级以及修改其他资料的方法

这期主要是讲一些&#xff0c;大家修改遇到的问题以及修改其他参数。 宝石、武器如何修改以及软件的安装与配置&#xff0c;请看我gg分栏的前两章 第一点&#xff1a;先讲一下自己武器上宝石等级的问题 宝石的代码&#xff1a; 0级升星宝石的代码1480e010 0级火力宝石的代码1…

概率论与数理统计:第一章:随机事件及其概率

文章目录 概率论Ch1. 随机事件及其概率1.基本概念(1)随机试验、随机事件、样本空间(2)事件的关系和运算①定义&#xff1a;互斥(互不相容)、对立②运算法则&#xff1a;德摩根率 (3)概率的定义(4)概率的性质(5)概率计算排列组合 2.等可能概型1.古典概型 (离散)2.几何概型 (连续…

Python-OpenCV 图像的基础操作

图像的基础操作 获取图像的像素值并修改获取图像的属性信息图像的ROI区域图像通道的拆分及合并图像扩边填充图像上的算术运算图像的加法图像的混合图像的位运算 获取图像的像素值并修改 首先读入一副图像&#xff1a; import numpy as np import cv2# 1.获取并修改像素值 # 读…

商用服务机器人公司【Richtech Robotics】申请纳斯达克IPO上市

来源&#xff1a;猛兽财经 作者&#xff1a;猛兽财经 猛兽财经获悉&#xff0c;总部位于美国内华达州拉斯维加斯由华人领导的商用服务机器人公司【Richtech Robotics】近期已向美国证券交易委员会&#xff08;SEC&#xff09;提交招股书&#xff0c;申请在纳斯达克IPO上市&am…

springboot互联网智慧3D导诊系统源码 智慧医疗系统源码

互联网智慧3D导诊系统源码 通过智能导诊&#xff0c;进行自助问询及挂号服务&#xff0c;减轻导诊台护士压力&#xff0c;挂号更加方便快捷。 技术架构&#xff1a;springbootredismybatis plusmysqlRocketMQ 智慧导诊系统开发原理 导诊系统从原理上大致可分为基于规则模板…

《深度探索c++对象模型》第六章笔记

非原创&#xff0c;在学习 6 执行期语意学(Runtime Semantics) 有这样一个简单的案例&#xff1a; if (yy xx.getValue()) {// ... } 其中&#xff0c;xx和yy的定义为&#xff1a; X xx; Y yy; class Y定义为&#xff1a; class Y { public:Y();~Y();bool operator(con…

目前Java后端就业前景怎么样?

前言 并不乐观&#xff0c;看看现在的就业形式就知道了&#xff0c;基本上是僧多粥少的情况&#xff0c;你可能会看到很多编程语言排行榜或者流行榜中Java的排名很高&#xff0c;如同下面这种&#xff1a; 看排名确实可以粗略的得知语言当下的流行度、使用率&#xff0c;但是它…

对齐控制大作战:align-content 和 align-items,到底谁才是真正的垂直大将?

&#x1f9d1;‍&#x1f4bc; 个人简介&#xff1a;一个不甘平庸的平凡人&#x1f36c; &#x1f5a5;️ Node专栏&#xff1a;Node.js从入门到精通 &#x1f5a5;️ TS知识总结&#xff1a;十万字TS知识点总结 &#x1f449; 你的一键三连是我更新的最大动力❤️&#xff01;…

算法通过村第二关-链表黄金笔记|K个一组反转

文章目录 前言链表反转|K个一组翻转链表解题方法&#xff1a;头插法处理&#xff1a;穿针引线法处理&#xff1a; 总结 前言 提示&#xff1a;没有人天生就喜欢一种气味而讨厌另一种气味。文明的暗示而已。 链表反转|K个一组翻转链表 给你链表的头节点 head &#xff0c;每 k…

毕业后想往开发上位机的方向发展,不知道怎么样?

上位机的薪资目前还可以&#xff0c;虽然不能比肩互联网&#xff0c;但是在所有行业中应该还算比较高的&#xff0c;二十几万的年薪比较容易。 还不错&#xff0c;最流行的开发方式有labview和C#&#xff0c;建议选C#。工控主要还是集中在长三角和珠三角&#xff0c;搞上位机很…

CentOS 安装 Jenkins

本文目录 1. 安装 JDK2. 获取 Jenkins 安装包3. 将安装包上传到服务器4. 修改 Jenkins 配置5. 启动 Jenkins6. 打开浏览器访问7. 获取并输入 admin 账户密码8. 跳过插件安装9. 添加管理员账户 1. 安装 JDK Jenkins 需要依赖 JDK&#xff0c;所以先安装 JDK1.8。输入以下命令&a…

为什么亚马逊购物车会丢失呢?如何找回来呢?

想要找回店铺购物车&#xff0c;必须先清楚购物车丢失的原因&#xff0c;只有找到原因&#xff0c;对症下药&#xff0c;才能以最快的速度找回。 一、亚马逊购物车丢失的原因&#xff1a; 1.listing新上架 通常来说&#xff0c;新上架的Listing&#xff0c;该Listing在亚马逊…

HIVE优化之不需要参数优化

#1.数据倾斜 什么是数据倾斜&#xff1f; 一部分数据多 一部分数据少 造成的结果&#xff1a; MR运行过慢 主要是shuffle和reduce过程慢 分组聚合导致数据倾斜 Hive未优化的分组聚合 方法1&#xff1a;在MAP端直接聚合&#xff08;分组聚合优化&#xff09;&#xff0c;减少…

wxwidgets Ribbon使用wxRibbonToolBar实例

wxRibbonToolBar就是工具栏&#xff0c;一下是实现的效果&#xff0c;界面只是功能展示&#xff0c;没有美化 实现代码如下所示&#xff1a; MyFrame::MyFrame(const wxString& title) : wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, wxSize(800, 600)) …

超越传统线程:探索Java Loom协程(纤程/虚拟线程)的无限潜力

《超越传统线程&#xff1a;探索Java Loom协程(纤程/虚拟线程)的无限潜力》 一、Java协程的发展历程 Java协程的发展历程可以概括为以下几个阶段&#xff1a; 1963年&#xff0c;协程的概念被正式提出&#xff0c;它的诞生甚至早于线程。2007年&#xff0c;Kilim项目发布&…

【C++】map和set在OJ中的应用

文章目录 前言1. 剑指 Offer &#xff1a; 复杂链表&#xff08;带随机指针&#xff09;的复制1.1 思路分析&#xff08;利用map搞&#xff09;1.2 AC代码 2. 前K个高频单词2.1 思路1AC代码2.2 思路2AC代码2.3 思路3AC代码 3. 两个数组的交集3.1 思路分析3.2 AC代码 前言 上一篇…

AI和ChatGPT:人工智能的奇迹

AI和ChatGPT&#xff1a;人工智能的奇迹 引言什么是人工智能&#xff1f;ChatGPT&#xff1a;AI的语言之王ChatGPT的工作原理ChatGPT的优势和挑战AI和ChatGPT的未来展望结论 引言 人工智能&#xff08;Artificial Intelligence&#xff0c;简称AI&#xff09;是一项令人兴奋的…

收集到大量的名片怎么转为excel?

来百度APP畅享高清图片 参加完展会或集体会议&#xff0c;是不是收了一大堆名片&#xff0c;保管起来超级麻烦&#xff0c;还容易丢三落四&#xff1f;别急&#xff0c;我们有办法&#xff01;把名片转成电子版保存到电脑上就完美啦&#xff01;但要是名片数量有点多&#xff0…

Linux文本三剑客之awk

目录 前言 awk 1.认识awk 2.使用awk 2.1语法 2.2常用命令选项 2.3awk变量 2.3.1内置变量 2.3.2自定义变量 2.4printf命令 awk例题 前言 awk、grep、sed是linux操作文本的三大利器&#xff0c;合称文本三剑客&#xff0c;也是必须掌握的linux命令之一。三者的功能都是…

什么是全局代理,手机怎么设置全局代理

目录 什么是全局代理 全局代理的优缺点 优点 缺点 手机怎么设置全局代理 注意事项 总结 在计算机网络和信息安全中&#xff0c;全局代理是一种常用的技术手段&#xff0c;用于将网络流量通过代理服务器进行转发和处理。本文将介绍什么是全局代理&#xff0c;探讨全局代理…