一、概念
简单来讲,信号量是一个用来描述临界资源的资源个数的计数器。
信号量的本质是一种数据操作锁,它本身不具有数据交换的功能,而是通过控制其他的通信资源(文件、外部设备等)来实现进程间通信,
他本身更只是一种外部资源的标识。信号量在此过程中负责数据操作的同步、互斥等功能。
当请求 一个使用信号量来表示 的资源时,进程需要先读取信号量的值来判断资源是否可用。大于0,资源可以请求;等于0,无资源可用,
进程会进入睡眠状态直至资源可用。
当进程不再使用一个使用信号量控制的共享资源时,信号量的值+1,对信号量的值进行的增减都是原子操作,这是由于信号量的主要作用
是维护资源的互斥或多进程的同步访问。而在信号量的创建及初始化上,不能保证操作均为原子性。
原子操作:
所谓原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch (切换到另
一个线程)。共享数据(全局变量或堆变量)的自增(++)操作在多线程环境下会出现错误是因为这个操作(一条c语句)被编译为汇编代码后不
止一条指令,因此在执行的时候可能执行了一半就被调度系统打断,去执行别的代码。
我们把单指令的操作称为原子的(Atomic),因为无论如何,单条指令的执行是不会被打断的。
总而言之,原子操作的状态是要么不做,要么做并且完成,没有做了但没有完成这种状态。
二、为什么要使用信号量
为了防止出现因多个程序同时访问一个共享资源而引发的一系列问题,我们需要一种方法,他可以通过生成并使用令牌来授权,在任意时刻只能
由一个执行线程访问代码的临界区域。临界区域是指执行数据更新的代码需要独占的执行。而信号量就可以提供这样的一 种访问机制让一个临界区
域同一时间只有一个线程访问它,也就是说信号量使用来协调进程对共享资源的访问的。其中共享内存的使用就要用到信号量。
三、信号量的工作原理:
我们这里讲的是二元信号量:
二元信号量(Binary Semaphore)是最简单的一种锁,它只用两种状态:占用与非占用。它适合只能被唯一一个线程访问的资源。当二元信号量
处于非占用状态时,第一个试图获取该二元信号量的线程会获得该锁,并将二元信号量置为占用状态,此后其他的所有试图获取该二元信号量的线程将会
等待,知道该锁被释放。
由于信号量只能进行两种操作:等待和发送信号,即 P(sv)和 V(sv)。它们的行为是这样的:
P(sv):若sv值大于0,就给它减1,;若他的值为0,就挂起该进程的执行
V(sv)如果有其他进程因等待sv而被挂起,就让他恢复运行;如果没有进程因等待而被挂起,那就给他们加1。
举个例子:
就是两个进程共享信号量sv,一旦其中一个进程执行了P(sv)操作,他将得到信号量,并可以进入临界区,使sv减1,。而第二个进程将被阻止进入临界
区,因为当它试图执行P(sv)时,sv为0,他会被挂起以等待第一个进程离开临界区并执行V(sv)释放信号量,这时第二个进程就可以恢复运行。
四、相关函数
1、创建信号量集(每次创建一个多多个信号量)
头文件:#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/sem.h>
函数原型:int semget(key_t key,int nsems,int semflg);
返回值:调用成功返回IPC标识符,调用失败返回-1;
参数:
key:所创建或打开信号量集的键值,也可以有函数 ftok 生成(不同于fork)
nsems:表示创建的信号量集中信号量的个数,该参数只在创建信号量集时有效
semflg: IPC_CREAT:如果IPC存在,则打开它,若不存在,则创建IPC资源。
IPC_EXCL:不能单独使用,没有意义;当与IPC_CREAT搭配使用时表示:若IPC不存在则创建IPC资源,若存在,则产生错 误。二者一 起使用用来保证所得的对象是新建的,而不是打开已有的对象。
2、执行在信号量集上的控制操作
头文件:#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/sem.h>
函数原型:int semctl(int semid,int semnum,int cmd, …);
返回值:调用成功返回一个正数,调用失败返回-1。
参数:semid:信号量集IPC标识符,因为信号量一般是作为一个信号量集使用的,而不是一个单独的信号量。所以在信号量集的操作中,不但要知道IPC关键字值,也要知道信号量集中的具体的信号量。
semnum:操作信号在信号量集中的编号
cmd:系统设置了三种操作:
IPC_STAT:读取一个信号量集的数据结构semid_ds,并将其储存在semun中的buf参数。
IPC_SET:设置信号量集的数据结构senid_ds中的元素ipc_perm,其值取自semun中的buf参数。
IPC_RMID:将信号量集从内存中删除。
3、操作信号量
头文件:#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/sem.h>
函数原型:int semop(int semid,struct sembuf * sops,unsigned nsops);
返回值:调用成功返回0,调用失败返回-1。
参数:semid:信号量集标识码
nsops:信号结构的数量,恒大于或等于1.
sops:指向寻出信号操作结构的数组指针,信号操作结构的原型如下:
struct sembuf
{
unsigned short sem_num; //操作信号在信号量集中的编号,第一个编号为0.
short sem_op;
short sem_flg; //信号操作标志,可能的选择有两种:
IPC_NOWAIT:对信号的操作不能满足时,semop()不会阻塞,并立即返回,同时设定错误信息;
SEM_UNDO:程序结束时(无论正不正常结束),保证信号值会被重设为semop()调用前的值。这样做的目的在于避免程序在异常情况下结束时未将锁定的资源解锁,造成该资源永远锁定。
};
五、代码实现: