訊號燈
訊號量用於同步兩個或多個程序之間的操作。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
編譯和執行該程式每次都會給你相同的輸出。