訊號燈

訊號量用於同步兩個或多個程序之間的操作。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

編譯和執行該程式每次都會給你相同的輸出。