| 函數 | 原型 | 作用 | 返回值 |
sem_init | int sem_init(sem_t *sem, int pshared, unsigned int value) | 初始化信號量,pshared=0 線程間,非0 進程間 | 成功 0,失敗 -1 |
sem_destroy | int sem_destroy(sem_t *sem) | 銷毀信號量(僅 sem_init 初始化的) | 成功 0,失敗 -1 |
sem_wait | int sem_wait(sem_t *sem) | P 操作:計數器>0 則 -1 並返回;=0 則阻塞 | 成功 0,失敗 -1 |
sem_trywait | int sem_trywait(sem_t *sem) | 非阻塞 P 操作,不可用即返回 EAGAIN | 成功 0,失敗 -1 |
sem_timedwait | int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout) | 帶超時的 P 操作 | 成功 0,超時 ETIMEDOUT |
sem_post | int sem_post(sem_t *sem) | V 操作:計數器 +1,喚醒阻塞線程 | 成功 0,失敗 -1 |
sem_getvalue | int sem_getvalue(sem_t *sem, int *sval) | 獲取當前計數器值(調試用) | 成功 0 |
實戰:模擬雙窗口服務系統(生產者-消費者變體)
場景:營業廳 2 個服務窗口,10 位顧客到達,窗口滿則等待,空閒則服務 100ms。
/* sem_demo.c */
#include
#include
#include
#include
#include
sem_t sem; // 全局信號量,線程間共享
void *get_service(void *arg) {
int customer_id = *((int *)arg); // 立即拷貝,避免主線程變量變更
if (sem_wait(&sem) == 0) { // P 操作:獲取窗口
usleep(100000); // 模擬服務 100ms
printf("customer %d receive service ...\n", customer_id);
sem_post(&sem); // V 操作:釋放窗口
}
return NULL;
}
#define CUSTOMER_NUM 10
int main() {
sem_init(&sem, 0, 2); // 初始值 2:兩個窗口
pthread_t customers[CUSTOMER_NUM];
for (int i = 0; i < CUSTOMER_NUM; i++) {
int *id = malloc(sizeof(int));
*id = i;
if (pthread_create(&customers[i], NULL, get_service, id) != 0) {
perror("pthread_create");
exit(1);
}
printf("Customer %d arrived.\n", i);
usleep(10000); // 錯峰到達
}
for (int i = 0; i < CUSTOMER_NUM; i++) {
pthread_join(customers[i], NULL);
}
sem_destroy(&sem);
return 0;
}
編譯與運行
gcc sem_demo.c -lpthread -o sem_demo
./sem_demo
運行結果分析(每次隨機調度)
Customer 0 arrived.
Customer 1 arrived.
customer 0 receive service ...
Customer 2 arrived.
customer 1 receive service ...
Customer 3 arrived.
customer 2 receive service ...
...(最多同時 2 個 customer receive service)
驗證點:同時服務顧客數從不超過 2,符合信號量初始值語義。
關鍵細節避坑
- 傳遞線程參數:主線程循環變量
i 地址傳給多線程,線程讀取時可能已變更。必須 malloc 拷貝 或用結構體傳值。
- 信號量銷毀時機:必須在所有線程
pthread_join 後調用 sem_destroy,否則殘留線程訪問已銷毀信號量導致未定義行為。
- 進程間共享:
sem_init(&sem, 1, 2) 需將信號量放在共享內存(mmap/shm_open),否則各進程擁有獨立副本。
- 錯誤處理:所有 API 必須檢查返回值,
sem_wait 可能被信號中斷返回 EINTR,生產代碼需循環重試。
擴展:有名信號量(進程間、無關進程)
sem_t *sem = sem_open("/my_sem", O_CREAT, 0644, 2); // 創建/打開
sem_wait(sem); // 使用同無名信號量
sem_close(sem); // 關閉描述符
sem_unlink("/my_sem"); // 系統級刪除
適用於無親緣關係進程同步,持久化於內核,進程退出不消失。