信號量核心概念
信號量是 Dijkstra 提出的同步原語,本質是一個 非負整數計數器,支援兩種 原子操作:
- P 操作 (wait):計數器 > 0 時遞減;= 0 時阻塞進程
- V 操作 (signal):有進程等待則喚醒;否則計數器遞增
三大核心函數詳解
semget - 獲取/建立信號量集
int semget(key_t key, int nsems, int semflg);
| 參數 | 說明 |
| key | IPC 鍵值,通常由 ftok() 產生 |
| nsems | 信號量個數,單一信號量通常為 1 |
| semflg | 權限標誌(如 0666 \| IPC_CREAT) |
返回:成功返回信號量集識別碼 (semid),失敗返回 -1
semop - 信號量操作 (P/V 原語)
int semop(int semid, struct sembuf *sops, size_t nsops);
sembuf 結構體:
struct sembuf {
unsigned short sem_num; // 信號量編號 (0-based)
short sem_op; // 操作值:+1 (V), -1 (P)
short sem_flg; // 通常為 SEM_UNDO
};
- 原子性保證:單次呼叫中的所有操作要么全做要么全不做
- SEM_UNDO:進程異常終止時自動撤銷操作,防止死鎖
semctl - 信號量控制
int semctl(int semid, int semnum, int cmd, ...);
常用命令:
| 命令 | 用途 | 第四參數 |
| SETVAL | 初始化信號量值 | union semun { int val; } |
| IPC_RMID | 刪除信號量集 | 不需要 |
| GETVAL | 獲取當前值 | 不需要 |
完整使用流程
// 1. 定義聯合體 (Linux 必須)
union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
};
// 2. 建立/獲取信號量
int semid = semget(ftok(".", 's'), 1, 0666 | IPC_CREAT);
// 3. 初始化為 1 (互斥鎖)
union semun arg;
arg.val = 1;
semctl(semid, 0, SETVAL, arg);
// 4. P 操作
struct sembuf p_op = {0, -1, SEM_UNDO};
semop(semid, &p_op, 1);
// 臨界區程式碼...
// 5. V 操作
struct sembuf v_op = {0, +1, SEM_UNDO};
semop(semid, &v_op, 1);
// 6. 刪除 (僅最後一個使用者調用)
semctl(semid, 0, IPC_RMID);
實戰封裝建議
// 封裝 P/V 操作
int sem_p(int semid) {
struct sembuf op = {0, -1, SEM_UNDO};
return semop(semid, &op, 1);
}
int sem_v(int semid) {
struct sembuf op = {0, +1, SEM_UNDO};
return semop(semid, &op, 1);
}
// 封裝初始化/刪除
int sem_init(int semid, int value) {
union semun arg = {.val = value};
return semctl(semid, 0, SETVAL, arg);
}
int sem_destroy(int semid) {
return semctl(semid, 0, IPC_RMID);
}
執行結果驗證
$ ./a.out -s & # 初始化並進入臨界區
[1] 2713
[pid: 2713] init semaphore
[pid: 2713] running nocritical code
$ ./a.out # 嘗試進入臨界區,被阻塞
[pid: 2714] running nocritical code
[pid: 2713] enter critical area
[pid: 2713] running critical code
[pid: 2713] leave critical area
[pid: 2714] enter critical area # 獲取鎖後進入
[pid: 2714] running critical code
[pid: 2714] leave critical area
$ [pid: 2713] delete semaphore
[1]+ Done ./a.out -s
常見陷阱與避坑
- 忘記定義 union semun:Linux 內核頭檔未提供,必須自行定義
- 信號量值溢出:短整型限制最大值 32767,高併發場景需注意
- 未使用 SEM_UNDO:進程崩潰導致信號量洩漏,造成永久阻塞
- 重複刪除:多進程競爭刪除,應由最後一個使用者清理
- 鍵值衝突:使用 ftok() 而非硬編碼 key_t
- 上一篇文章: 如何客觀看待譚浩強《C程序設計》:歷史貢獻與時代局限的辯證思考
- 下一篇文章: 信號量原理與 P/V 操作詳解:進程同步互斥的基石
Copyright ©
程式碼之家 All Rights Reserved