Windows XP Windows 7 Windows 2003 Windows Vista Windows 綜合教學 Linux 系統教學
Windows 10 Windows 8 Windows 2008 Windows NT Windows Server 電腦軟體教學

Linux 線程同步機制:信號量與互斥鎖區別及 POSIX 信號量實戰

編輯:Linux 系統常見問題解答

多線程編程中,共享資源訪問同步是核心難點。互斥鎖 只允許單線程進入臨界區,信號量 允許計數器控制的多線程並發訪問。本文深度解析兩者區別、POSIX 信號量 API、完整生產者-消費者模型實戰代碼,含編譯運行驗證。

核心區別:互斥鎖 vs 信號量

POSIX 信號量 API 速查

特性互斥鎖信號量
允許並發訪問線程數1N(由計數器決定)
適用場景獨佔資源保護(如鏈表操作)有限資源池(連接池、線程池、讀者-寫者)
所有權有(僅加鎖線程可解鎖)無(任意線程可 post)
優先級反轉保護支援優先級繼承/天花板協議標準信號量無,需額外機制
進程間共享需設置 pthread_mutexattr_setpshared原生支援(pshared 參數)

實戰:模擬雙窗口服務系統(生產者-消費者變體)

場景:營業廳 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,符合信號量初始值語義。

關鍵細節避坑

  1. 傳遞線程參數:主線程循環變量 i 地址傳給多線程,線程讀取時可能已變更。必須 malloc 拷貝 或用結構體傳值。
  2. 信號量銷毀時機:必須在所有線程 pthread_join 後調用 sem_destroy,否則殘留線程訪問已銷毀信號量導致未定義行為。
  3. 進程間共享:sem_init(&sem, 1, 2) 需將信號量放在共享內存(mmap/shm_open),否則各進程擁有獨立副本。
  4. 錯誤處理:所有 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"); // 系統級刪除

適用於無親緣關係進程同步,持久化於內核,進程退出不消失。

Copyright © Windows教程網 All Rights Reserved
函數原型作用返回值
sem_initint sem_init(sem_t *sem, int pshared, unsigned int value)初始化信號量,pshared=0 線程間,非0 進程間成功 0,失敗 -1
sem_destroyint sem_destroy(sem_t *sem)銷毀信號量(僅 sem_init 初始化的)成功 0,失敗 -1
sem_waitint sem_wait(sem_t *sem)P 操作:計數器>0 則 -1 並返回;=0 則阻塞成功 0,失敗 -1
sem_trywaitint sem_trywait(sem_t *sem)非阻塞 P 操作,不可用即返回 EAGAIN成功 0,失敗 -1
sem_timedwaitint sem_timedwait(sem_t *sem, const struct timespec *abs_timeout)帶超時的 P 操作成功 0,超時 ETIMEDOUT
sem_postint sem_post(sem_t *sem)V 操作:計數器 +1,喚醒阻塞線程成功 0,失敗 -1
sem_getvalueint sem_getvalue(sem_t *sem, int *sval)獲取當前計數器值(調試用)成功 0