目录
一、前言
二、认识信号量
1、信号量的概念
2、信号量的作用
三、信号量相关函数
1、semget()函数
2、semctl()函数
3、semop()函数
四、主函数测试
1、工程一
2、工程二
3、先执行工程一立马执行工程二
Tip
回顾一下关于进程间的通信(IPC,InterProcess Communication)我们学习了哪些
- 1️⃣信号 :信号的使用
- 2️⃣管道:管道的使用
- 3️⃣共享内存:共享内存的使用
- 4️⃣消息队列:消息队列的使用
- 5️⃣套接字(socket):socket的使用
今天来学习一个新的IPC通信: 信号量
✅等待:P(semaphore variable)
如果信号量为1,则进行 -1 操作,若为 0 ,则挂起该进程。
✅信号:V(semaphore variable)
如果有其他进程因等待信号量而被挂起,就让它恢复执行;如果没有,则进行 +1 操作。
场景一
初始sv=1,进程一先访问信号量,进行P操作后sv=0,此时进程二也访问了信号量并想要执行P操作,但是由于此时sv=0,所以进程二只能先挂起,直至进程一执行完业务,进行V操作后,即sv+1=1后,进程二恢复执行。
场景二
进程一业务做完,执行V操作,sv+1=1,进程二恢复执行P操作,sv-1=0。
看完上述两个场景,可以发现信号量被一个进程占用的时候,另一个进程无法访问只能暂时挂起,是不是有点类似于线程锁?
因此我们可以发现信号量可以解决进程的同步问题,即当两个进程访问同一块内存区域时(假设同时访问同一个共享内存),如果两个进程同时操作里面的数据时,这时候就会出现数据不安全的问题,使用信号量可以让一个进程先执行,另一个挂起,直至第一个进程执行完业务,第二个进程再执行业务。
下面我们来学习一下信号量的相关函数
头文件
#include
#include
#include函数原型
int semget(key_t key, int nsems, int semflg);
参数
✅key:一个整数值,不相关的进程将通过这个值去访问同一个信号量。
✅nsems:需要使用的信号量个数,一般为1。
✅semflg:权限位,类似于文件的权限位,一般为(IPC_CREAT | 0777)。
返回值
✔️成功:返回一个正数值 ,即 semid 。
❌失败:returns -1 ;
int sem_create(key_t key, int nsems)
{
int res = semget(key, nsems, IPC_CREAT | 0777);
if (res < 0)
{
perror("semget error");
}
return res;
}
头文件
#include
#include
#include函数原型
int semctl(int semid, int semnum, int cmd, ...);
参数
✅semid:semget () 函数的返回值。
✅semnum:信号量的编号,一般取值为 0,表示这是第一个也是唯一的信号量。
✅cmd:将要采取的操作动作。
✅...:如果还有第四个参数,那它将是一个“union semun”复合结构
返回值
✔️成功:returns 0 ;
❌失败:returns -1 ;
- semctl函数里的command可以有许多不同的值,下面这两个是比较常用的:
SETVAL:用来把信号量初始化为一个已知的值,这个值在semun结构里是以val成员的面目传递的。
IPC_RMID:删除一个已经没有人继续使用的信号量标识码
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的值,来设置信号量具体值。
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) */
};
//信号量设置值
int sem_setval(int semid, int senindex, int val)
{
//senindex:信号量的编号,一般取值为 0
union semun arg;
arg.val = val;//设置具体值
int res = semctl(semid, senindex, SETVAL, arg);
if (res < 0)
{
perror("semctl error");
}
return res;
}
头文件
#include
#include
#include函数原型
int semop(int semid, struct sembuf *sops, size_t nsops);
参数
✅semid:semget () 函数的返回值。
✅sops:是个指向一个结构体 struct sembuf 的指针。
✅nsops:一般为1。
返回值
✔️成功:returns 0 ;
❌失败:returns -1 ;
struct sembuf
{
//信号量的编号,一般为0。
short sem_num;
//一般只会用到两个值,P操作 -1,V操作 1。
short sem_op;
//sem_flg通常被设置为SEM_UNDO
short sem_flg;
};
//信号量p操作 -1
int sem_p(int semid, int semindex)
{
//senindex:信号量的编号,一般取值为 0
struct sembuf buf = { semindex, -1, SEM_UNDO };
int res = semop(semid, &buf, 1);
if (res < 0)
{
perror("semop error");
}
return res;
}
//信号量v操作 +1
int sem_v(int semid, int semindex)
{
struct sembuf buf = { semindex, 1, SEM_UNDO };
int res = semop(semid, &buf, 1);
if (res < 0)
{
perror("semop error");
}
return res;
}
int main()
{
//如果1001信号量存在则访问 不存在则创建
int semid = sem_create((key_t)1001, 1);
//将信号量数组下标为0的数据设置为1
sem_setval(semid, 0, 1);
//加锁 信号量数组下标为0 执行P操作后信号量的值为0
sem_p(semid, 0);
for (int i = 0; i < 5; i++)
{
cout << "第一个进程正在运行…… i = " << i << endl;
sleep(1);
}
//解锁,执行V操作后 信号量的值为1
sem_v(semid, 0);
return 0;
}
int main()
{
//如果1001信号量存在则访问 不存在则创建
int semid = sem_create((key_t)1001, 1);
//加锁 -1
sem_p(semid, 0);//阻塞无法继续执行 只有上一个工程结束后才执行
for (int i = 0; i < 5; i++)
{
cout << "第二个进程正在运行…… i = " << i << endl;
sleep(1);
}
//解锁 +1
sem_v(semid, 0);
return 0;
}
- 可以发现执行第一个工程后,立马执行第二个工程(此时第二个工程被挂起),只有当第一个工程结束,第二个工程才恢复执行。
- 类似于线程“加锁”,解决进程同步问题。
在linux终端
- 输入ipcs, 可以查看创建的信号量
- 输入 ipcrm -a,可以删除创建的信号量
The end ……
原创不易,转载请标明出处。
对您有帮助的话可以一键三连,会持续更新的(嘻嘻)。