一、信号量
信号量:有时被称为信号灯,是在多线程环境下使用的一种设施,是可以用来保证两个或多个关键代码段不被并发调用。在进入一个关键代码段之前,线程必须获取一个信号量;一旦该关键代码完成了。那么该线程必须释放信号量。其它想进入该关键代码段的线程必须等待直到第一个线程释放信号量。为了完成这个过程,需要创建一个信号量VI,然后将Acquire Semaphore VI以及Release Semaphore VI分别放置在每个关键代码的首末端,确认这些信号量VI引用的是初始创建的信号量。
二、信号量的分类
二进制信号量(binary semaphone): 只允许信号量取0或1值,其同时只能被一个线程获取。
整型信号量(integer semaphone):信号量取值是整数,它可以被多个线程同时获得,直到信号量的值变为0。
记录型信号量(record semaphone):每个信号量s除一个整数值value(计数)外,还有一个等待队列List,其中是阻塞在该信号量的各个线程的标识。当信号量被释放一个,值被加一后,系统自动从等待队列中唤醒一个等待中的线程,让其获得信号量,同时信号量再减一。
三、信号量的工作原理
这里主要讨论的是二进制信号量的工作原理。
信号量是一个特殊的变量,程序对它防问都是原子操作,且只允许对它进行等待(即P(信号变量))和发送(即V(信号变量))信息操作,二进制的信号变量只能取0和1的变量。
P(sv): 如果sv的值大于0,就给它减1;如果它的值为0,就挂起该进程的执行。
V(sv): 如果有其他进程因等待sv而挂起,就让它恢复运行,如果没有进程因等待sv而挂起,就给他加1。
四、操作函数
1.创建一个信号量
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key,int nsems,int semflag);
返回值:成功返回信号量集的IPC标识符。失败返回-1.
参数:
key:所创建或打开信号量集的键值。
nsems:创建的信号量集中的信号量的个数,该参数只在创建信号量集时有效。
semflag:调用函数的操作类型,也可以用于设置信号量集的访问权限,两个通过or表示。
2.删除信号量
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semctl(int semid,int semum,int cmd,..)
返回值:成功返回0,失败返回-1。
参数:
semid:信号量的标志码,也就是semget()函数的返回值。
semum:操作信号在信号集中的编号。从0开始。
cmd:表示要进行的操作,删除时将cmd设置成IPC_RMID。
当这个函数有第四个参数时,类型是union,它的结构如下:
union semun
{
int val; //设置信号量的初始值
struct semid_ds *buf; //IPC_STAT、IPC_SET 使用缓存区
unsigned short *array; //GETALL、SETALL 使用的数组
struct seminfo *__buf; //IPC_INFO 使用缓存区
};
3.改变信号量的值
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semop(int semid, struct sembuf *sops, unsigned nsops);
该函数是进行P,V操作时用到的函数。
返回值:成功返回0;失败返回-1。
参数:
semid:semget的返回值。
sops:指向存储信号操作结构的数组指针,信号操作结构的原型如下:
struct sembuf {
unsigned short sem_num; //信号在信号集的索引
short sem_op; //操作类型(P操作,V操作)
short sem_flg; //操作标志,在设置P,V操作时一般设置成为SEM_UNDO
};
当操作信号量(semop)时,sem_flg可以设置SEM_UNDO标识;SEM_UNDO用于将修改的信号量值在进程正常退出(调用exit退出或main执行完)或异常退出(如段异常、除0异常、收到KILL信号等)时归还给信号量。
如信号量初始值是20,进程以SEM_UNDO方式操作信号量减2,减5,加1;在进程未退出时,信号量变成20-2-5+1=14;在进程退出时,将修改的值归还给信号量,信号量变成14+2+5-1=20。
nsops:信号操作结构的数量,恒大与或等于1。
五、信号量的实现
sem.h:
#ifndef _SEM_H_
#define _SEM_H_
#define PATHNAME "."
#define PROJ_ID 0x6666
#include<stdio.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/sem.h>
union semun
{
int val; //使用的值
struct semid_ds *buf; //IPC_STAT、IPC_SET 使用缓存
unsigned short *array; //AETALL、SETALL 使用的数组
struct seminfo *__buf; //IPC_INFO(Linux特有)使用缓存区
};
int ComSem(int nums,int flag);
static int op_sem(int semid,int op,int which);
int CreateSem(int nums);
int DestroySem(int semid);
int GetSem(int nums);
int InitSem(int semid,int which);
int P(int semid,int which);
int V(int semid,int which);
int InitSem(int semid,int which);
#endif
sem.c
#include"sem.h"
int ComSem(int nums,int flag)
{
key_t key=ftok(PATHNAME,PROJ_ID);
if(key<0)
{
perror("ftok");
return -1;
}
int semid=semget(key,nums,flag|0666);
if(semid<0)
{
perror("semget");
return -2;
}
return semid;
}
static int op_sem(int semid,int op,int which)
{
struct sembuf sem;
sem.sem_num=which;
sem.sem_op=op;
return semop(semid,&sem,1);
}
int P(int semid,int which)
{
int ret=op_sem(semid,-1,which);
if(ret==0)
return 0;
else
return -1;
}
int V(int semid,int which)
{
int ret=op_sem(semid,1,which);
if(ret==0)
return 0;
else
return -1;
}
int CreateSem(int nums)
{
return ComSem(nums,IPC_CREAT|IPC_EXCL);
}
int GetSem(int nums)
{
return ComSem(nums,IPC_CREAT);
}
int DestroySem(int semid)
{
if(semctl(semid,0,IPC_RMID)<0)
{
perror("semctl");
return -1;
}
}
int InitSem(int semid,int which)
{
union semun sem;
sem.val=1;
int ret=semctl(semid,which,SETVAL,sem);
if(ret<0)
{
perror("semctl");
return -1;
}
return 0;
}
main.c
#include"sem.h"
int main()
{
int semid=CreateSem(12);
if(semid<0)
{
perror("semid");
return -1;
}
InitSem(semid,1);
sleep(5);
//DestroySem(semid);
pid_t id=fork();
if(id==0)
{
//child
while(1)
{
P(semid,1);
printf("A");
fflush(stdout);
usleep(100000);
printf("A ");
fflush(stdout);
usleep(100000);
V(semid,1);
}
}
else
{
//father
while(1)
{
P(semid,1);
printf("B");
fflush(stdout);
usleep(100000);
printf("B ");
fflush(stdout);
usleep(100000);
V(semid,1);
}
}
wait(NULL);
return 0;
}