目录
一.信号量的概念
1.信号量的定义
为了防止出现因多个程序同时访问临界资源而引发的一系列问题,我们需要一种方法,它可以通过生成并使用令牌来授权,在任一时刻只能有一个执行线程访问代码的临界区。而信号量就可以提供这样的一种访问机制,让一个临界区同一时间只有一个线程在访问它,也就是说信号量是用来调协进程对临界资源的访问的。
- 临界资源:指同一时刻,只允许一个进程(或线程)访问的资源
- 临界区:指访问临界资源的代码段
例如:通常我们上公共卫生间时都会有一个提示灯,如果提示灯的颜色是绿色的则代表此时卫生间没有人在使用,因此任意一个人都可以来使用卫生间,但如果此时卫生间的提示灯是红色的,则代表此时卫生间正在被其他人使用,如果有其他人想要使用卫生间,则这个人需要等待前一个人使用完卫生间。
2.对信号量的操作
我们对信号量只可以进行两种操作,p操作和v操作
- p操作:当信号量值为0时,进程阻塞;当信号量值大于0时,信号量减1,进程获得资源继续运行。
- v操作:将信号量值加1,不会发生阻塞,v操作代表释放资源
例子:如果此时有两个进程a和b要访问某一临界资源,用一个信号量来协调这两个进程对这一临界资源的访问。假设信号量的初始值为1,若此时进程a要对临界资源进行访问,则a进程首先会获取此时信号量的值,而此时信号量的值为1,则a进程访问临界资源,且同时对信号量进行p操作,信号量的值变为0。
若此时进程b也要访问临界资源,则它首先会获取信号量的值,而此时信号量的值为0,则进程b无法访问临界资源,接着进程b进入阻塞状态。
最后,进程a访问完临界资源后,对信号量进行v操作,信号量的值变为1,接着进程b退出阻塞状态,开始对临界资源进行访问,且同时对信号量进行p操作,此时信号量的值为0。
问题一
在上述例子中,如果进程a在访问临界资源且对信号量进行p操作时(此时p操作还没有完成,说明此时信号量的值还为1),进程b也要对临界资源进行访问怎么办?
答案是肯定的,就是进程b不会被允许访问临界资源。原因是信号量是一种特殊的变量,访问具有原子性。
- 原子操作:指该操作绝不会在执行完毕前被任何其他任务或事件打断,也就是说,它是最小的执行单位,不能有比它更小的执行单元。
也就是说,在进程a完成对信号量的p操作以前,其他任何进程都无法访问此信号量。
3.信号量的类型
- 计数信号量:计数信号量的初始值其实就是共享资源的数量。有几个进程访问,信号量就减去几,直到再次没有资源可以使用。及信号量的初始值不为1。
- 二值信号量:二值信号量可以理解成计数信号量的一种特殊形式,即初始化为仅有一个资源可以使用,当临界资源被获取了,信号量值就是 0,临界资源被释放,信号量值就是 1,把这种只有 0 和 1 两种情况的信号量称之为二值信号量。及信号量的初始值为1。
二.信号量的使用
1.头文件
<sys/sem.h>
2.semget函数
semget函数的作用是创建一个新信号量或获取一个已有信号量的键。
#include <sys/sem.h>
int semget(key_t key, int num_sems, int sem_flags);
参数
- key:信号量键值,可以理解为信号量的唯一性标记。不相关的进程可以通过它访问一个同一个信号量。例如上述例子中,进程a和b怎么知道在那么多的信号量当中,他们二者访问的是同一个信号量呢?就是通过这个key值。
- num_sems:信号量的数目,一般为 1
- sem_flags:有两个值,IPC_CREATE 和 IPC_EXCL,IPC_CREATE表示若信号量已存在,返回该信号量标识符。IPC_EXCL表示若信号量已存在,返回错误。
返回值
成功则返回相应的信号量标识符,失败返回-1
3.semop函数
semop函数用于改变信号量的值
#include <sys/sem.h>
int semop(int sem_id, struct sembuf *sem_opa, size_t num_sem_ops);
参数
- sem_id:由semget返回的信号量标识符;
- sem_opa:表示一个由sembuf结构表示的信号量操作数组;
- num_sem_ops:规定该数组中操作的数量。
sembuf(信号量操作数组)
struct sembuf{ // 除非使用一组信号量,否则它为0,一般从0,1,...num_secs-1 unsigned short sem_num; // 信号量在一次操作中需要改变的数据,通常是两个数,一个是-1, // 即P(等待)操作,一个是+1,即V(发送信号)操作。 short sem_op; // 通常为SEM_UNDO,使操作系统跟踪信号, // 并在进程没有释放该信号量而终止时,操作系统释放信号量。 short sem_flg; }
返回值
成功返回0,失败返回-1
4.semctl函数
semctl用来直接控制信号量信息,常用来进行信号量的初始化和销毁。
#include <sys/sem.h>
int semctl(int sem_id, int sem_num, int command, [union semun sem_union]);
参数:
- sem_id:信号量标识符;
- sem_num:信号量的值,一般取值为0;
- command:通常是下面两个值中的其中一个:
- SETVAL:用来把信号量初始化为一个已知的值。p 这个值通过union semun中的val成员设置,其作用是在信号量第一次使用前对它进行设置;(初始化信号量)
- IPC_RMID:用于删除一个已经无需继续使用的信号量标识符。(删除信号量)
- sem_union:可选参数,是一个union semun结构,它至少包含以下几个成员:
union semun{
int val;
struct semid_ds *buf;
unsigned short *array;
}
一般用到的是val,表示要传给信号量的初始值。注意:semun联合结构必须由程序员自己定义。
返回值
成功返回0,失败返回-1。
三.信号量的具体应用
sem.h
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <sys/sem.h>
void sem_init();
void sem_p();
void sem_v();
void sem_destroy();
sem.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <sys/sem.h>
#include "sem.h"
union semun // man semctl
{
int val;
};
static int sem_id;
void sem_init()
{
sem_id = semget((key_t)1234, 1, IPC_CREAT | IPC_EXCL | 0600); // 尝试创建一个信号量
if(sem_id == -1) // 信号量已存在或者创建失败
{
sem_id = semget((key_t)1234, 1, 0600); // 获取已有信号量的id
if(sem_id == -1) // 判断是否成功获得信号量id
{
perror("semget erorr");
return;
}
}
else
{
union semun a;
a.val = 1;
if(semctl(sem_id, 0, SETVAL, a) == -1) // 信号量赋初值
{
perror("semctl error");
}
}
}
void sem_p()
{
struct sembuf buf;
buf.sem_num = 0;
buf.sem_op = -1; // p操作
buf.sem_flg = SEM_UNDO;
if(semop(sem_id, &buf, 1) == -1)
{
perror("semop error");
}
}
void sem_v()
{
struct sembuf buf;
buf.sem_num = 0;
buf.sem_op = 1; // v操作
buf.sem_flg = SEM_UNDO;
if(semop(sem_id, &buf, 1) == -1)
{
perror("semop error");
}
}
void sem_destroy()
{
if(semctl(sem_id, 0, IPC_RMID) == -1)
{
perror("semctl error");
}
}
a.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "sem.h"
int main(int argc, char *argv[])
{
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);
n = rand() % 3;
sleep(n);
sem_v();
}
exit(0);
}
b.c
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #include <assert.h> #include "sem.h" int main(int argc, char *argv[]) { 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); } sleep(10); sem_destroy(); exit(0); }
接着执行命令:
运行结果:
四.总结
优点:可以同步进程
缺点:信号量的数目有限