注意请不要把它与信号混淆起来, 信号与信号量是不同的两种事物。 为了防止出现因多个程序同时访问一个共享资源而引发的一系列问题, 我们需要一种方法, 它可以通过生成并使用令牌来授权, 在任一时刻只能有一个执行线程访问代码的临界区域。 临界区域是指执行数据更新的代码需要独占式地执行。 而信号量就可以提供这样的一种访问机制, 让一个临界区同一时间只有一个线程在访问它, 也就是说信号量是用来调协进程对共享资源的访问的。
信号量是一个特殊的变量, 程序对其访问都是原子操作, 且只允许对它进行等待(即 P(信号变量))和发送(即 V(信号变量))信息操作。 最简单的信号量是只能取 0 和 1 的变量, 这也是信号量最常见的一种形式,叫做二进制信号量。 而可以取多个正整数的信号量被称为通用信号量。 这里主要讨论二进制信号量。
由于信号量只能进行两种操作等待和发送信号, 即 P(sv)和 V(sv),他们的行为是这样的:
P(sv): 如果 sv 的值大于零, 就给它减 1; 如果它的值为零, 就挂起该进程的执行
V(sv): 如果有其他进程因等待 sv 而被挂起, 就让它恢复运行, 如果没有进程因等待 sv 而挂起, 就给它加 1。
举个例子, 就是两个进程共享信号量 sv, 一旦其中一个进程执行了 P(sv)操作, 它将得到信号量, 并可以进入临界区, 使 sv 减 1。 而第二个进程将被阻止进入临界区, 因为当它试图执行 P(sv)时, sv 为 0, 它会被挂起以等待第一个进程离开临界区域并执行 V(sv)释放信号量, 这时第二个进程就可以恢复执行。
信号灯也叫信号量, 它能够用来同步进程的动作, 不能传输数据。 它的应用场景就像红绿灯, 控制各进程使用共享资源的顺序。 Posix 无名信号灯用于线程同步, Posix 有名信号灯, System V 信号灯。 信号灯相当于一个值大于或等于 0 计数器, 信号灯值大于 0, 进程就可以申请资源, 信号灯值-1, 如果信号灯值为0, 一个进程还想对它进行-1, 那么这个进程就会阻塞, 直到信号灯值大于 1。
使用 System V 信号灯的步骤如下:
1. 使用 semget()创建或打开一个信号灯集。
2. 使用 semctl()初始化信号灯集 。
3. 使用 semop()操作信号灯值, 即进行 P/V 操作。
P 操作: 申请资源, 申清完后信号灯值-1;
V 操作: 释放资源, 释放资源后信号灯值+1;
Linux 提供了一组精心设计的信号量接口来对信号进行操作, 它们不只是针对二进制信号量, 下面将会对这些函数进行介绍, 但请注意, 这些函数都是用来对成组的信号量值进行操作的。 它们声明在头文件sys/sem.h 中。
函数 | int semget(key_t key, int nsems, int semflg) |
头文件 | #include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> |
参数 key_t key | 信号量的键值 |
参数 int nsems | 信号量的数量 |
参数 int semflg | 标识 |
返回值 | 成功返回信号量的 ID, 失败返回-1 |
功能 | 创建一个新信号量或取得一个已有信号量 |
函数 | int semctl(int semid, int semnum, int cmd, union semun arg) |
头文件 | #include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> |
参数 int semid | 信号量 ID |
参数 int semnum | 信号量编号 |
参数 cmd | IPC_STAT(获取信号量的属性) IPC_SET(设置信号量的属性)IPC_RMID (删除信号量) SETVAL(设置信号量的值) |
参数 arg | union semun { int val; struct semid_ds *buf; unsigned short *array; struct seminfo *__buf; } |
功能 | 初始化信号灯集合 |
函数 | int semop(int semid, struct sembuf *sops, size_t nsops) |
头文件 | #include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> |
参数 int semid | 信号量 ID |
参数 struct sembuf *sops | 信号量结构体数组 |
参数 size_t nsops | 要操作信号量的数量 struct sembuf{ unsigned short sem_num; //要操作的信号量的编号 short sem_op; //P/V 操作, 1 为 V 操作, 释放资源。 -1 为 P 操作, 分配资源。 0 为等待, 直到信号量的值变成 0 short sem_flg; //0 表示阻塞, IPC_NOWAIT 表示非阻塞 } |
功能 | 在信号量上执行一个或多个操作。 |
实验代码:
指定哪个进程运行,可以使用进程间通信的知识, 或者使用信号量, 这里以使用信号量为例:
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <unistd.h>
union semun
{
int val;
};
int main(void)
{
int semid;
int key;
pid_t pid;
struct sembuf sem;
union semun semun_union;
key = ftok("./a.c", 0666);
semid = semget(key, 1, 0666 | IPC_CREAT);
semun_union.val = 0;
semctl(semid, 0, SETVAL, semun_union);
pid = fork();
if (pid > 0)
{
sem.sem_num = 0;
sem.sem_op = -1;
sem.sem_flg = 0;
semop(semid, &sem, 1);
printf("This is parents\n");
sem.sem_num = 0;
sem.sem_op = 1;
sem.sem_flg = 0;
semop(semid, &sem, 1);
}
if (pid == 0)
{
sleep(2);
sem.sem_num = 0;
sem.sem_op = 1;
sem.sem_flg = 0;
semop(semid, &sem, 1);
printf("This is son\n");
}
return 0;
}
编译运行程序如下图所示:
信号量是一个特殊的变量, 程序对其访问都是原子操作, 且只允许对它进行等待(即 P(信号变量))和发送(即 V(信号变量))信息操作。 我们通常通过信号来解决多个进程对同一资源的访问竞争的问题, 使在任一时刻只能有一个执行线程访问代码的临界区域, 也可以说它是协调进程间的对同一资源的访问权, 也就是用于同步进程的。