信号量
一、信号量的定义
引入信号量:
为了防止出现因多个程序同时访问一个共享资源而引发的一系列问题,我们需要一种方法,它可以通过生成并使用令牌来授权,在任一时刻只能有一个执行线程访问代码的临界区域。临界区域是指执行数据更新的代码需要独占式地执行。而信号量就可以提供这样的一种访问机制,让一个临界区同一时间只有一个线程在访问它,也就是说信号量是用来调协进程对共享资源的访问的。
1、信号量是一种特殊的变量,加一(v)、减一(p)都是一个原子操作,当信号量值减到0时,进程就会发生阻塞。
2、什么叫原子操作?
答:就是操作在执行的过程中不能被打断的操作就叫原子操作。
3、临界资源
答:同一时刻只允许一个(有限个)进程访问的资源。
4、临界区
答:访问临界资源的代码段。
5、信号量的分类
(1)二值信号量 取值:0 1
(2)计数信号量 取值:可能>1
二、信号量的函数
1、semget函数
它的作用是创建一个新信号量或取得一个已有信号量,原型为:
int semget(key_t key, int num_sems, int sem_flags);
(1)第一个参数key是整数值(唯一非零),不相关的进程可以通过它访问一个信号量,它代表程序可能要使用的某个资源,程序对所有信号量的访问都是间接的,程序先通过调用semget函数并提供一个键,再由系统生成一个相应的信号标识符(semget函数的返回值),只有semget函数才直接使用信号量键,所有其他的信号量函数使用由semget函数返回的信号量标识符。如果多个程序使用相同的key值,key将负责协调工作。
(2)第二个参数num_sems指定需要的信号量数目,它的值几乎总是1。
(3)第三个参数sem_flags是一组标志,当想要当信号量不存在时创建一个新 的信号量,可以和值IPC_CREAT做按位或操作。设置了IPC_CREAT标志后,即使给出的键是一个已有信号量的键,也不会产生错误。而IPC_CREAT | IPC_EXCL则可以创建一个新的,唯一的信号量,如果信号量已存在,返回一个错误。
semget函数成功返回一个相应信号标识符(非零),失败返回-1。
2、semop函数
它的作用是改变信号量的值,原型为:int semop(int sem_id, struct sembuf *sem_opa, size_t num_sem_ops);
sem_id是由semget返回的信号量标识符,sembuf结构的定义如下:
struct sembuf
{
short sem_num;//除非使用一组信号量,否则它为0
short sem_op;//信号量在一次操作中需要改变的数据,通常是两个数,一个是-1,即P(等待)操作,
//一个是+1,即V(发送信号)操作。
short sem_flg;//通常为SEM_UNDO,使操作系统跟跟踪信号
//并在进程没有释放该信号量而终止时,操作系统释放信号量
};
3、semctl函数
该函数用来直接控制信号量信息,它的原型为:
int semctl(int sem_id, int sem_num, int command, ...);
如果有第四个参数,它通常是一个union semum结构,定义如下:
union semun{
int val;
struct semid_ds *buf;
unsigned short *arry;
};
前两个参数与前面一个函数中的一样,command通常是下面两个值中的其中一个
SETVAL:用来把信号量初始化为一个已知的值。p 这个值通过union semun中的val成员设置,其作用是在信号量第一次使用前对它进行设置。
IPC_RMID:用于删除一个已经无需继续使用的信号量标识符。
示例:
下面使用一个例子来说明进程间如何使用信号量来进行通信,这个例子是两个相同的程序同时向屏幕输出数据,我们可以看到如何使用信号量来使两个进程协调工作,使同一时间只有一个进程可以向屏幕输出数据。
代码如下:
sem.h
#include
#include
#include
#include
union semun
{
int val;
};
void sem_init();
void sem_p();
void sem_v();
void sem_destroy();
sem.c
#include "sem.h"
static int semid = 0;
void sem_init()
{
semid = semget((key_t)1234,1,IPC_CREAT | IPC_EXCL | 0600);
if(semid == -1)
{
semid = semget((key_t)1234,1,0600);
if(semid == -1)
{
perror("semget error");
}
}
else
{
union semun a;
a.val = 1;
if(semctl(semid,0,SETVAL,a) == -1)
{
perror("semctl error\n");
}
}
}
void sem_p()
{
struct sembuf buf;
buf.sem_num = 0;
buf.sem_op = -1;
buf.sem_flg = SEM_UNDO;
if(semop(semid,&buf,1) == -1)
{
perror("semop p error");
}
}
void sem_v()
{
struct sembuf buf;
buf.sem_num = 0;
buf.sem_op = 1;
buf.sem_flg = SEM_UNDO;
if(semop(semid,&buf,1) == -1)
{
perror("semop v error");
}
}
void sem_destroy()
{
if(semctl(semid,0,IPC_RMID) == -1)
{
perror("semctl error");
}
}
A进程a.c
#include
#include
#include
#include
#include
#include
#include "sem.h"
int main()
{
sem_init();
int i = 0;
for(;i < 5;i++)
{
sem_p();
printf("A");
fflush(stdout);
int n = rand() % 3;
sleep(n);
printf("A");
fflush(stdout);
sem_v();
n = rand() % 3;
sleep(n);
}
sleep(10);
sem_destroy();
}
B进程b.c
#include
#include
#include
#include
#include "sem.h"
int main()
{
sem_init();
int i = 0;
for(;i < 5;i++)
{
sem_p();
printf("B");
fflush(stdout);
int n = rand() % 3;
sleep(n);
printf("B");
fflush(stdout);
sem_v();
n = rand() % 3;
sleep(n);
}
}
运行结果: