信号量与共享内存实现进程间通信(生产者消费者问题为例)

(一)信号量

信号量是IPC的一种,可以看做是一个计数器,计数值为可用的共享资源的数量,信号量可用于多进程的同步,为多个进程提供对共享资源的访问。


linux下的信号量的接口函数如下:


/*(1)获取信号量*/
int semget(key_t key, int semnum, int flag); 
semnum为信号数量,如果是新创建信号量,则大于0;如果是打开已有的信号量,则semnum可为0;
例子:
int semid = semget((ket_t)29111, 2, 0060|IPC_CREAT);

/*(2)对信号量进行操作*/
int semctl(int semid, int semnm, int cmd [, union semun arg]);
参数semnm是信号量集合中某个信号量的下标,从0开始,在0与nsems-1之间;
参数中联合体结构如下(有些系统需要自定义):
union semun 
{
   int val;                 /*for SETVAL*/
   struct semid_ds *buf;    /*for IPC_STAT  IPC_SET*/
   unsigned short *array;   /*for SETALL*/
};

这里union semun arg是可选参数,例如cmd=IPC_RMID就不需要该参数,cmd为SETVAL或者SETALL的时候需要该参数。

以cmd=SETALL时,此时信号量集合的值由arg.array数组指定。

例子:
union semun arg;
unsigned short semvalArr[SEM_NUM] = {1, 0};
arg.array = semvalArr;		
ret = semctl(semid, 0, SETALL, arg);
/*(3)自动执行信号量集合上的操作数组 semop是原子操作*/
int semctl(int semid, int semnm, int cmd [, union semun arg]);
其中机构体struct sembuf在(sys/sem.h)中定义如下:
struct sembuf
{
   unsigned short sem_num;      /*0~nsems-1*/
   short sem_op;                /*negative, 0, pasitive*/
   short sem_flg;               /*0 IPC_UNDO  IPC_NOWAIT*/
};
输入域参数semoparray指向一个信号量操作结构体数组,参数nops是操作的信号量的个数。struct sembuf结构体参数含义如下:
sem_num: 信号量序号(0与nsems-1之间);
sem_flg: 值为0是正常操作;值为IPC_NOWAIT是非阻塞模式;IPC_UNDO会在信号量调整值的过程中,同步调整信号量调整值,以便某个进程异常退出的情况下,信号量能够恢复到原值,否则其他进程可能会一直阻塞而死锁;
sem_op: 取值为正时候,调用该函数信号量值自动加上sem_op(如果是IPC_UNDO模式,信号量调整值减去sem_op),其含义可理解为释放sem_op个共享资源;取值为负的时候,如果信号量的算术值(可能为负)大于或等于sem_op的绝对值,则信号量自动减去sem_op的绝对值(ICP_UNDO模式下信号量调整值加sem_op的绝对值),表示占有sem_op个资源;如果信号量的算术值(可能为负)小于sem_op的绝对值,表明剩余的资源(信号量值表示)少于申请的资源,此时如果指定了IPC_NOWAIT则报错,否则进程被挂起直到信号量的算术值(可能为负)大于或等于sem_op的绝对值被唤醒(挂起和唤醒是系统自动进行的);如果sem_op为0,则表明希望等到该信号量值变为0,如果不是可能会报错或者阻塞。

这里可以看用semop可以包装P、V操作,以便实现进程之间的同步,这里回顾一下PV操作。
定义一个信号量sem,则有:
P操作:令sem=sem-1,若sem>=0,则进程继续执行,否则挂起等待,加入等待队列。

V操作:令sem=sem+1,若sem<0,唤醒等待队列中的一个进程。(注意:sem<0才需要唤醒,因为此时有阻塞; s>=0说明没阻塞也不需要唤醒)。


再来看一个经典的生产者消费者同步问题:生产者进程产生消息放在某个共享资源区,共享资源区的消息不为空时消费者进程从该共享资源区读取消息,为了避免脏读生产者和消费者不能同时访问共享资源,这就涉及到生产者和消费者进程之间的同步。利用信号量解决该问题:
定义两个信号量(不能是一个,否则生产者或者消费者可能会无限的访问共享资源,形成死循环)S1、S2,初始值为1,0。S1=1表示生产者进程可写共享资源,为1则不可;S2=1表示消费者进程可读共享资源,为0则不可。伪代码描述如下:
生产者进程:
whlie(true)
{
    产生一个消息
	P(S1)
	消息写到共享资源
	V(S2)
}

消费者进程:
whlie(true)
{
	P(S2)
	从共享资源读取消息
	V(S1)
	处理消息
}
这里需要注意的是以上必须生产者先启动,否则会死锁,如果需要消费者先启动,需要进行额外的控制。上述代码中的P、V操作可以用semop进行包装, 一个例子如下(信号量为二元信号量):
/*sem P*/ 
int sem_p(int semid, int semnum)  
  • 5
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值