问题引入
当我们想让我们的程序同时只能被一个进程运行时,我们需要使用到信号量来实现.
何为信号量
信号量,是一种特殊的变量。只能对信号量执行P操作和V操作
P操作, 如果信号量的值 > 0, 则把该信号量减1
如果信号量的值 ==0, 则挂起该进程。
V操作: 如果有进程因该信号量而被挂起,则恢复该进程运行
如果没有进程因该信号量而挂起,则把该信号量加1
注意:P操作、V操作都是原子操作,即其在执行时,不会被中断。
注意:此指的“信号量”是指System V IPC的信号量,与线程所使用的信号量不同。该信号量,用于进程间通信。
相关API
- 信号量的获取
semget
原型:int semget(key_t key, int nsems, int semflg);
功能:获取一个已存在的、或创建一个新的信号量量,返回该信号量的标识符
参数:key, 键值,该键值对应一个唯一的信号量。类似于共享内存的键值。
不同的进程可通过该键值和semget获取唯一的信号量。特殊键值:IPC_PRIVAT该信号量只允许创建者本身, 可用于父子进程间通信。
nsems, 需要的信号量数目,一般取1
sem_flags, 与共享内存的sem_flags类似。IPC_CREAT, 如果该信号量未存在,则创建该信号量如果该信号量已存在,也不发送错误。
返回值: 成功,则返回一个正数
失败, 返回返回-1
- 信号量的操作
semop
原型:int semop(int semid, struct sembuf *sops, unsigned nsops);
功能:改变信号量的值,即对信号量执行P操作、或V操作。
参数:semid, 信号量标识符, 即semget的返回值
sops, 是一个数组,元素类型为struct sembuf
struct sembuf {
short sem_num; //信号量组中的编号(即指定对哪个信号量操作)
//semget实际是获取一组信号量
//如果只获取了一个信号量,则该成员取0
short sem_op; // -1, 表示P操作
// 1, 表示V操作
short sem_flg; // SEM_UNDO : 如果进程在终止时,没有释放信号量
// 如果不设置指定标志,应该设置为0 则,自动释放该信号量
}
nsops, 表示第二个参数sops所表示的数组的大小,即表示有几个struct sembuf
返回值: 失败, 返回-1
成功, 返回 0
- 信号量的控制
semctl
原型:int semctl(int semid, int sem_num, int cmd, …);
功能:对信号量进行控制
参数:semid, 信号量标识符
sem_num, 信号量组中的编号,如果只有一个信号量,则取0
cmd, SETVAL 把信号量初始化为指定的值,具体的值由第4个参数确定
注意:只能对信号量初始化一次,如果在各进程中,分别对该信号量进行初始化,则可能导致错误!
IPC_RMID 删除信号量
参数4, 类型为:union semun {
int val; // SETVAL命令要设置的值
struct semid_ds *buf;
unsigned short *array;
}
注意:union semun类型要求自己定义有些Linux发行版在sys/sem.h中定义,有些发行版则没有定义。
可自定义如下:
#if defined(GNU_LIBRARY) && !defined(_SEM_SEMUN_UNDEFINED)
#else
union semun {
int val;
struct semid_ds *buf;
unsigned short int *array;
struct seminfo *__buf;
};
#endif
测试demo
预期实现
打开两个终端,同时运行这段程序.会观察到一个进程阻塞的效果,当一个进程在执行这段程序时,另一个进程阻塞在外面. 当次进程执行到 v 操作时代表 此进程执行完毕,阻塞在外的进程立即开始执行,运行到 p 操作时,进程开始阻塞 只允许允许此进程
demo源码
#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<sys/sem.h>
#include<stdlib.h>
#include<unistd.h>
#include<stdio.h>
#if defined(__GNU_LIBRARY__) && !defined(_SEM_SEMUN_UNDEFINED)
#else
union semun {
int val;
struct semid_ds *buf;
unsigned short int *array;
struct seminfo *__buf;
};
#endif
static int sem_inital(int semid){
int ret;
union semun semun;
semun.val = 1;
ret = semctl(semid,0,SETVAL,semun);
if(ret == -1){
fprintf(stderr,"semctl failed!\n");
}
return ret;
}
static int sem_p(int semid){
int ret;
struct sembuf sembuf;
sembuf.sem_op = -1;
sembuf.sem_num = 0;
sembuf.sem_flg = SEM_UNDO;
ret = semop(semid,&sembuf,1);
if(ret == -1){
fprintf(stderr,"sem_p failed\n");
}
return ret;
}
static int sem_v(int semid){
int ret;
struct sembuf sembuf;
sembuf.sem_op = 1;
sembuf.sem_num = 0;
sembuf.sem_flg = SEM_UNDO;
ret = semop(semid,&sembuf,1);
if(ret==-1){
fprintf(stderr,"sem_v failde!\n");
}
return ret;
}
int main(int argc,char *argv[]){
int i;
int ret;
int semid;
/*获取信号*/
semid = semget((key_t)1234,1,0666|IPC_CREAT);
if(semid == -1){
printf("semid get failed\n");
exit(1);
}
/*初始化信号*/
if(argc>1){
ret = sem_inital(semid);
if(ret == -1){
exit(1);
}
}
/*这个循环便于我们观察执行的效果,for 循环交替阻塞执行函数, p 操作 v操作 交替进行,会看到这样一个效果 本来是 sleep 5s 之后打印数据,会看到一个 "爆破式的" 打印,当一个执行到 v 操作,那个等候多时的进程的程序 立即执行
*/
for(i=0;i<5;i++){
if(sem_p(semid)==-1){
exit(1);
}
printf("Process(%d) In\n",getpid());
sleep(5);
printf("Process(%d) Out\n",getpid());
if(sem_v(semid)==-1){
exit(1);
}
sleep(1);
}
return 0;
}
测试效果
总结
信号量的作用是想让一个程序不能被指定个数的进程同时执行,项目中给的 demo 是测试的 不能被 2 个进程同时执行.