信号灯
信号量用于同步两个或多个进程之间的操作。POSIX 定义了两组不同的信号量函数:
- ‘System V IPC’ -
semctl()
,semop()
,semget()
。 - ‘POSIX 信号量’ -
sem_close()
,sem_destroy()
,sem_getvalue()
,sem_init()
,sem_open()
,sem_post()
,sem_trywait()
,sem_unlink()
。
本节描述了 System V IPC 信号量,因为它们源自 Unix System V.
首先,你需要包含所需的标头。旧版本的 POSIX 需要 #include <sys/types.h>
; 现代 POSIX 和大多数系统都不需要它。
#include <sys/sem.h>
然后,你需要在父项和子项中定义一个键。
#define KEY 0x1111
这个密钥在两个程序中都必须相同,否则它们不会引用相同的 IPC 结构。有一些方法可以生成一个商定的密钥而无需对其值进行硬编码。
接下来,根据你的编译器,你可能需要也可能不需要执行此步骤:为信号量操作声明一个联合。
union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
};
接下来,定义你的 try (semwait
)和 raise (semsignal
)结构。名字 P 和 V 来自荷兰语
struct sembuf p = { 0, -1, SEM_UNDO}; # semwait
struct sembuf v = { 0, +1, SEM_UNDO}; # semsignal
现在,首先获取 IPC 信号量的 id。
int id;
// 2nd argument is number of semaphores
// 3rd argument is the mode (IPC_CREAT creates the semaphore set if needed)
if ((id = semget(KEY, 1, 0666 | IPC_CREAT) < 0) {
/* error handling code */
}
在父级中,初始化信号量以使计数器为 1。
union semun u;
u.val = 1;
if (semctl(id, 0, SETVAL, u) < 0) { // SETVAL is a macro to specify that you're setting the value of the semaphore to that specified by the union u
/* error handling code */
}
现在,你可以根据需要减少或增加信号量。在关键部分的开头,你使用 semop()
函数递减计数器:
if (semop(id, &p, 1) < 0) {
/* error handling code */
}
要增加信号量,可以使用 &v
代替 &p
:
if (semop(id, &v, 1) < 0) {
/* error handling code */
}
请注意,每个函数在成功时返回 0
,在失败时返回 -1
。不检查这些返回状态可能会导致破坏性问题。
例 1.1:用线程赛车
下面的程序将有一个过程 fork
一个孩子,父母和孩子都试图在没有任何同步的情况下将字符打印到终端上。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int main()
{
int pid;
pid = fork();
srand(pid);
if(pid < 0)
{
perror("fork"); exit(1);
}
else if(pid)
{
char *s = "abcdefgh";
int l = strlen(s);
for(int i = 0; i < l; ++i)
{
putchar(s[i]);
fflush(stdout);
sleep(rand() % 2);
putchar(s[i]);
fflush(stdout);
sleep(rand() % 2);
}
}
else
{
char *s = "ABCDEFGH";
int l = strlen(s);
for(int i = 0; i < l; ++i)
{
putchar(s[i]);
fflush(stdout);
sleep(rand() % 2);
putchar(s[i]);
fflush(stdout);
sleep(rand() % 2);
}
}
}
输出(第一次运行):
aAABaBCbCbDDcEEcddeFFGGHHeffgghh
(第二轮):
aabbccAABddBCeeCffgDDghEEhFFGGHH
编译和运行此程序应该每次都给你一个不同的输出。
例 1.2:避免使用信号量赛车
修改例 1.1 以使用信号量,我们有:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#define KEY 0x1111
union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
};
struct sembuf p = { 0, -1, SEM_UNDO};
struct sembuf v = { 0, +1, SEM_UNDO};
int main()
{
int id = semget(KEY, 1, 0666 | IPC_CREAT);
if(id < 0)
{
perror("semget"); exit(11);
}
union semun u;
u.val = 1;
if(semctl(id, 0, SETVAL, u) < 0)
{
perror("semctl"); exit(12);
}
int pid;
pid = fork();
srand(pid);
if(pid < 0)
{
perror("fork"); exit(1);
}
else if(pid)
{
char *s = "abcdefgh";
int l = strlen(s);
for(int i = 0; i < l; ++i)
{
if(semop(id, &p, 1) < 0)
{
perror("semop p"); exit(13);
}
putchar(s[i]);
fflush(stdout);
sleep(rand() % 2);
putchar(s[i]);
fflush(stdout);
if(semop(id, &v, 1) < 0)
{
perror("semop p"); exit(14);
}
sleep(rand() % 2);
}
}
else
{
char *s = "ABCDEFGH";
int l = strlen(s);
for(int i = 0; i < l; ++i)
{
if(semop(id, &p, 1) < 0)
{
perror("semop p"); exit(15);
}
putchar(s[i]);
fflush(stdout);
sleep(rand() % 2);
putchar(s[i]);
fflush(stdout);
if(semop(id, &v, 1) < 0)
{
perror("semop p"); exit(16);
}
sleep(rand() % 2);
}
}
}
输出:
aabbAABBCCccddeeDDffEEFFGGHHgghh
编译和运行该程序每次都会给你相同的输出。