18.进程间通信(四)
一、同步,互斥,临界资源,临界区概念
回归system V共享内存,IPC本质是:先让不同的进程看到同一份资源。但共享内存没有同步机制,因此对于数据没有保护机制,如何约束?解决方案:信号量。
多个执行流(进程),能看到同一份公共资源:共享资源。
被保护起来的共享资源叫做 临界资源。
在进程中涉及到互斥资源的程序叫做 临界区(访问临界资源对应的代码)
多个执行流访问临界资源具有一定的顺序性叫做 同步
要么做,要么不做叫做 原子性
对于临界区保护要采用加锁机制->锁本身也是共享资源,怎么保证锁的安全?->申请锁的时候,必须是原子的。
二、System V 信号量
1.信号量是什么?
本质是一个计数器,用来表明临界资源的数量。
2.理解信号量(电影院)
信号量实质:对于资源的预定机制。
想访问临界资源,必须先“买票”(申请信号量)->所有进程,访问临界资源的一小块,就必须申请信号量!
例子:把共享内存按照不同的区域,部分使用! -> 1.不要访问同一个位置 2.不要放入过多的进程进来 -> 并发访问,不出问题(多元信号量)
细节1:信号量本身就是共享资源,怎么保证安全?
申请--,原子性,P操作
sem++,原子性,V操作
细节2:信号量只有1和0两态的信号量,二元信号量。(就是互斥)
信号量与进程间通信
信号量和通信有什么关系?
1.先访问信号量P,每个进程得先看到同一个信号量(IPC本质:不同进程看到同一份资源)
2.不是传递数据,才是通信IPC,通知,互斥同步,也算!!(传递信号类,先后顺序)
熟悉信号量接口和系统调用:
int semget(key_t key, int nsems, int semflg); //获取信号量
key:标识信号量唯一性。
nsems:信号量集合的信号量个数
semflg:IPC_CREAT,IPC_EXCL,新建要带权限。
返回值:成功返回信号量集合标识符,失败返回-1,错误码被设置。
int semctl(int semid, int semnum, int cmd, ...); //初始化指定信号量,删除所有信号量
semid:信号量集合标识符
semnum:指定要控制的信号量集合中信号量的下标
cmd: SET_VAL 初始化指定信号量集合中下标为semnum的信号量,IPC_RMID 删除信号量
...:可变参数,SET_VAL时要传入结构体对象,用作参数初始化。结构如下:
union semun {int val; /* Value for SETVAL */struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */unsigned short *array; /* Array for GETALL, SETALL */struct seminfo *__buf; /* Buffer for IPC_INFO(Linux-specific) */ };
其中val为信号量计数器的数量。
int semop(int semid, struct sembuf *sops, size_t nsops); //对指定的信号量集合指定下标的信号量进行PV操作
semid:信号量集合标识符
sops:传入的结构体信息
nsops:信号量集合的信号量个数
sops传入的结构体如下:
struct sembuf {unsigned short sem_num; /* semaphore number */short sem_op; /* semaphore operation */short sem_flg; /* operation flags */ };
sem_num为信号量集合中信号量的下标。
semop为PV操作的类型,传入-1代表P操作,传入1代表V操作。
sem_flg为操作,设为SEM_UNDO。
信号量的结构以及信号量的组织方式
共享内存、消息队列、信号量->key区分唯一值!->OS中,共享内存、消息队列、信号量被当成了同一种资源!(System V IPC)
信号量基础实例
#ifndef SEM_V1_HPP #define SEM_V1_HPP#include <iostream> #include <memory> #include <vector> #include <cstdio> #include <cstdlib> #include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h>#define EXIT_ERROR(m) \do \{ \perror(m); \exit(1); \} while (0)class Sem { public:Sem(int semid) : _semid(semid){}~Sem(){int ret = semctl(_semid, 0, IPC_RMID);if (ret < 0){EXIT_ERROR("semctl");}std::cout << "~Sem() success!" << std::endl;}void P(){struct sembuf buf;buf.sem_num = 0;buf.sem_op = -1;buf.sem_flg = SEM_UNDO;int ret = semop(_semid, &buf, 1);if (ret < 0){EXIT_ERROR("semop");}}void V(){struct sembuf buf;buf.sem_num = 0;buf.sem_op = 1;buf.sem_flg = SEM_UNDO;int ret = semop(_semid, &buf, 1);if (ret < 0){EXIT_ERROR("semop");}}private:int _semid; };#define SEM_CREAT (IPC_CREAT | IPC_EXCL | 0666) #define SEM_GET (IPC_CREAT)const std::string path_default = "."; const int proj_id_default = 0x11;class SemBuilder { public:SemBuilder(){}~SemBuilder(){}SemBuilder *SetVal(int val){_val = val;return this;}std::shared_ptr<Sem> Build(int flag){// 1.创建/获取信号量集合key_t key = ftok(path_default.c_str(), proj_id_default);int semid = semget(key, 1, flag);if (semid < 0){EXIT_ERROR("semget");}std::cout << "sem create success! semid:" << semid << std::endl;// 2.只有父进程要初始化信号量集合if (flag == SEM_CREAT){Init(semid);std::cout << "Init success" << std::endl;}// 3.返回Sem对象std::shared_ptr<Sem> sp = std::make_shared<Sem>(semid);return sp;}private:void Init(int semid){union semun{int val; /* Value for SETVAL */struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */unsigned short *array; /* Array for GETALL, SETALL */struct seminfo *__buf; /* Buffer for IPC_INFO(Linux-specific) */} sun;sun.val = _val;int ret = semctl(semid, 0, SETVAL, sun);if (ret < 0){EXIT_ERROR("semctl");}}private:int _val; };#endif // SEM_V1_HPP
基于建造者模式的信号量
#ifndef SEM_V2_HPP #define SEM_V2_HPP#include <iostream> #include <memory> #include <vector> #include <cstdio> #include <cstdlib> #include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h>#define SEM_CREAT (IPC_CREAT | IPC_EXCL) #define SEM_GET (IPC_CREAT) #define EXIT_ERROR(m) \do \{ \perror(m); \exit(1); \} while (0)const std::string path_default = "."; const int proj_id_default = 0x11;// 产品类:最终构建的复杂对象 class Sem { public:Sem(int semid) : _semid(semid){}~Sem(){int ret = semctl(_semid, 0, IPC_RMID);if (ret < 0){EXIT_ERROR("semctl");}std::cout << "~Sem() success!" << std::endl;}void P(){struct sembuf buf;buf.sem_num = 0;buf.sem_op = -1;buf.sem_flg = SEM_UNDO;int ret = semop(_semid, &buf, 1);if (ret < 0){EXIT_ERROR("semop");}}void V(){struct sembuf buf;buf.sem_num = 0;buf.sem_op = 1;buf.sem_flg = SEM_UNDO;int ret = semop(_semid, &buf, 1);if (ret < 0){EXIT_ERROR("semop");}}private:int _semid; };// 抽象建造者:定义构建接口 class SemBuilder { public:virtual ~SemBuilder() = default;virtual void SetKey() = 0;virtual void SetVal(int val) = 0;virtual void SetMode(int mode) = 0;virtual void Build(int flag) = 0;virtual std::shared_ptr<Sem> GetSem() = 0; };// 具体建造者1:游戏电脑建造者 class SemBuilder1 : public SemBuilder { public:virtual void SetKey() override{_key = ftok(path_default.c_str(), proj_id_default);}virtual void SetVal(int val) override{_val = val;}virtual void SetMode(int mode) override{_mode = mode;}virtual void Build(int flag) override{int real = _mode == 0 ? flag : flag | _mode;int semid = semget(_key, 1, real);if (semid < 0){EXIT_ERROR("semget");}std::cout << "sem create success! semid:" << semid << std::endl;if (flag == SEM_CREAT){Init(semid);}_sp = std::make_shared<Sem>(semid);}virtual std::shared_ptr<Sem> GetSem() override{return _sp;}private:void Init(int semid){union semun{int val; /* Value for SETVAL */struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */unsigned short *array; /* Array for GETALL, SETALL */struct seminfo *__buf; /* Buffer for IPC_INFO(Linux-specific) */} sun;sun.val = _val;int ret = semctl(semid, 0, SETVAL, sun);if (ret < 0){EXIT_ERROR("semctl");}}private:int _key;int _val;int _mode;std::shared_ptr<Sem> _sp; };// 指挥者:控制构建流程 class SemEngineer { public:SemEngineer() = default;void Construct(std::shared_ptr<SemBuilder> spb,int flag, int mode = 0){spb->SetKey();spb->SetVal(1);spb->SetMode(mode);spb->Build(flag);} };#endif // SEM_V2_HPP