信号灯

信号量用于同步两个或多个进程之间的操作。POSIX 定义了两组不同的信号量函数:

  1. ‘System V IPC’ - semctl()semop()semget()
  2. ‘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;
};

接下来,定义你的 trysemwait)和 raisesemsignal)结构。名字 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

编译和运行该程序每次都会给你相同的输出。