信号量概述:
信号量和前面所说的IPC(管道,FIFO,消息队列等)有所不同,它是一个计数器,用于为多个进程提供对共享数据的访问,信号量是一种特殊的变量,访问具有原子性,
信号量只允许对它进行两个操作:
<1>等待信号量
当信号量的值为0时,程序等待;当信号量的值大于0时,信号量的值减一,程序继续运行;
<2>发送信号量
将信号量的值加一;
我们使用信号量,来解决进程或线程间共享资源引发的同步问题:
(1)测试控制该资源的信号量
(2)若此信号量的值为正,则进程可以使用该资源,在这种情况下,进程会将信号量值减一,表示使用了一个资源单位。
(3)否则,若此信号量的值为0,则进程进入休眠状态,直至信号量大于0,进程被唤醒后返回步骤(1)。
注:当进程不再使用由一个信号量控制的共享资源时,该信号量值加一,如果有进程正在休眠等待此信号,则唤醒他们,为了正确实现信号量,信号量值得测试和减一操作是原子操作,为此,信号量通常是在内核实现的。
一,信号量的使用
信号量相关API介绍
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);//获取信号量id或者创建信号量
//参数1为key值(通过ftok获取),
//参数2为该集合中的信号量数
//参数3为对信号量id操作创建信号量若存在返回id值(IPC_CREAT)
int semctl(int semid, int semnum, int cmd, ...);//用于信号量的初始化和删除
//semid 信号集表示符
//semnum 信号量数组上的下标,表示某一个信号量
//cmd 命令 :
//SET_VAL 用联合体中val成员的值设置信号量集合中单个信号量的值。
//IPC_RMID 从内核删除信号量
int semop(int semid, struct sembuf *sops, size_t nsops);//完成对信号量的P操作和V操作
//semid 信号量集标识符
//sops 指向进行操作的信号量集结构体数组的首地址
//nsops 进行操作信号量的个数,即sops结构变量的个数,须大于或者等于1,最常见的设置此值等于1,只完成对一个信号量的操作。
struct sembuf{
short semnum;
/*信号量集合中的信号量编号*/
short val;
/*若val>0进行V操作信号量加val,表示进程释放控制的资源 */
/*若val<0进行P操作信号量值减val,若(semval-val)<0(semval为该信号量值),则调用进程阻塞,直到资源可用;若设置IPC_NOWAIT不会睡眠,进程直接返回EAGAIN错误*/
/*若val==0时阻塞等待信号量为0,调用进程进入睡眠状态,直到信号值为0;若设置IPC_NOWAIT,进程不会睡眠,直接返回EAGAIN错误*/
short flag;
/*0 设置信号量的默认操作*/
/*IPC_NOWAIT设置信号量操作不等待*/
/*SEM_UNDO 选项会让内核记录一个与调用进程相关的UNDO记录,如果该进程崩溃,则根据这个进程的UNDO记录自动恢复相应信号量的计数值*/
};
相关代码实现:
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <unistd.h>
#include <stdlib.h>
union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
struct seminfo *__buf;
};
void P_get_handler(int id)
{
struct sembuf sops;
sops.sem_num = 0;//信号量集合中的信号量编号
sops.sem_op = -1;//表示资源被占用
sops.sem_flg = SEM_UNDO;//设置为阻塞当val == 0时
semop(id,&sops,1);
printf("get key !\n");
}
void V_ret_handler(int id )
{
struct sembuf sops;
sops.sem_num = 0;//信号量集合中的信号量编号
sops.sem_op = 1;//进程释放控制的资源
sops.sem_flg = SEM_UNDO;//当val值=0时阻塞
semop(id,&sops,1);
printf("put back key!\n");
}
int main()
{
key_t key;
int semid;
union semun initsem;
int pid;
key = ftok(".",'1');//获取key值
semid = semget(key,1,IPC_CREAT|0666)
if(semid == -1)
{
exit(0);
}
initsem.val = 0;//val =0,让先运行的进程阻塞
semctl(semid,0,SETVAL,initsem);//初始化信号量val的值
pid = fork();//父进程创建子进程
if(pid > 0 ){//父进程
get_handler(semid);//如果父进程先运行由于val=0会造成进程阻塞现象直到val>0会继续运行
printf("this is father!\n");
return_handler(semid);//运行完成后进行V操作
semctl(semid,0,IPC_RMID);//从内核中删除信号量
}else if(pid == 0){
// return_handler(semid);
printf("this is child!\n");
return_handler(semid);
}else if(pid == -1){//创建进程失败
printf("fork failed!\n");
}
return 0;
}
注:不管运行多少次,每次都是子进程先运行,后父进程再运行。最后从内核中删除信号量
tips:
程序的原子性:整个程序中所有的操作,要么全部完成,要么全部不完成。不能停滞在中间某个环节。
原子性操作:原子性在一个操作中是不可以中断的,要么全部执行成功要么全部执行失败,有着“同生共死”的感觉。即使在多个线程一起执行时,一个操作一旦开始,也不会被别的线程所干扰。