【linux】系統程式設計-6-POSIX標準下的訊號量與互斥鎖

李柱明發表於2021-01-19


前言

原文

8. POSIX訊號量

8.1 概念

  • 訊號量(Semaphore)是一種實現程式/執行緒間通訊的機制,可以實現程式/執行緒之間同步或臨界資源的互斥訪問, 常用於協助一組相互競爭的程式/執行緒來訪問臨界資源。
  • 在POSIX標準中分無名訊號量和有名訊號量:
    • 無名訊號量
      • 一般用於執行緒間同步或互斥
      • 無名訊號量儲存於記憶體中
    • 有名訊號量
      • 一般用於程式間同步或互斥
      • 有名訊號量保持於檔案中
  • 訊號量P、V操作
    • P操作(申請資源)
      • 如果有可用資源,則申請成功,訊號量減一
      • 如果沒有可用資源,則申請失敗,進入阻塞或返回
    • V操作(釋放資源)
      • 如果訊號量的等待佇列中有程式/執行緒在等待,則喚醒一個阻塞的程式/執行緒
      • 如果沒有程式/執行緒等待阻塞,則訊號量加一

8.2 POSIX無名訊號量

  • 無名訊號量直接存於記憶體中,不同程式之間不能互相訪問。fork程式中的無名訊號量於父程式中的無名訊號量是兩個獨立的訊號量。若非要無名訊號量用於程式間,則可把訊號量放在共享記憶體中。
  • 包含標頭檔案 #include semaphore.h
  • 相關函式
int sem_init(sem_t *sem, int pshared, unsigned int value);
int sem_destroy(sem_t *sem);
int sem_wait(sem_t *sem);
int sem_trywait(sem_t *sem);
int sem_post(sem_t *sem);
  • int sem_init(sem_t *sem, int pshared, unsigned int value); :初始化訊號量
  • int sem_destroy(sem_t *sem); :銷燬訊號量
  • int sem_wait(sem_t *sem); :P操作,帶阻塞。成功返回0,失敗返回-1
  • int sem_trywait(sem_t *sem); :P操作,不阻塞。成功返回0,失敗返回EAGAIN
  • int sem_post(sem_t *sem); :V操作。成功返回0,失敗返回-1

8.3 POSIX有名訊號量

  • 有名訊號量儲存於檔案中,一般用於程式間同步或互斥。其檔名類似 sem.[訊號量名字] ,建立該訊號量成功後,系統會將其存放在 /dev/shm
    中。程式退出後,該訊號量不會消失,需要手動刪除並釋放資源。
  • 包含標頭檔案 #include semaphore.h
  • 相關函式
sem_t *sem_open(const char *name, int oflag, mode_t mode, unsigned int value);
int sem_wait(sem_t *sem);
int sem_trywait(sem_t *sem);
int sem_post(sem_t *sem);
int sem_close(sem_t *sem);
int sem_unlink(const char *name);
  • sem_t *sem_open(const char *name, int oflag, mode_t mode, unsigned int value); :開啟或建立一個有名訊號量
  • int sem_wait(sem_t *sem); :P操作,帶阻塞。成功返回0,失敗返回-1
  • int sem_trywait(sem_t *sem); :P操作,不阻塞。成功返回0,失敗返回EAGAIN
  • int sem_post(sem_t *sem); :V操作。成功返回0,失敗返回-1
  • int sem_close(sem_t *sem); :關閉訊號量,表示當前程式取消對該訊號量的使用權。不影響其它程式/執行緒對其繼續使用。
  • int sem_unlink(const char *name); :刪除訊號量,其它程式/執行緒也訪問不了了。

8.4 POPSIX訊號量與system V訊號量的區別

  • 先簡單瞭解一下訊號量分類:
    1. 二值訊號量:其值為0或為1。
    2. 計數訊號量:其值為0至某個限制值(POSIX訊號量最大為32767)
    3. 計數訊號量集:一個或多個計數訊號量構成一個集合。
  • system V訊號量所指的是計數訊號量集,POSIX訊號量所指的是單個計數訊號量
  • system V訊號量,可以控制每次自增或自減的訊號量計數,而POSIX訊號量每次只能自增或自減1
  • system V訊號量提供的API是沒有下劃線的:semctl()、semget() 和 semop()
  • system V訊號量是核心持續的;POSIX無名訊號量是程式持續的;POSIX有名訊號量是核心持續

9. POSIX互斥鎖

9.1 概念

  • 當不同程式/執行緒去訪問某個臨界資源的時候,就需要進行互斥保護,這種互斥保護可以看做是一種鎖機制。好比上廁所,鎖住門,不讓別人進。
  • 互斥鎖和訊號量不同的是,它具有互斥鎖所有權、遞迴訪問等特性,常用於實現對臨界資源的獨佔式處理,任意時刻互斥鎖的狀態只有兩種,開鎖或閉鎖。
    • 互斥鎖所有權就是互斥鎖被執行緒持有時,互斥鎖處於閉鎖狀態,執行緒獲得互斥鎖的所有權;當該執行緒釋放互斥鎖時,該互斥鎖處於開鎖狀態,執行緒失去該互斥鎖的所有權。
    • 互斥鎖遞迴訪問,持有該互斥鎖的執行緒具有對該互斥鎖進行遞迴訪問。
  • 避免死鎖需要遵循的規則
    • 對共享資源操作前一定要獲得鎖
    • 完成操作後一定要釋放鎖
    • 儘量短時間佔用鎖
    • 如果有多鎖, 如獲得順序是ABC連環扣, 釋放順序也應該是ABC。
  • 互斥鎖比訊號量更適合的應用場景:
    • 保護臨界資源
    • 執行緒可能會多次獲取互斥鎖的情況下。這樣可以避免同一執行緒多次遞迴持有而造成死鎖的問題。
  • 這裡的POSIX互斥鎖用於執行緒間

9.2 初始化互斥鎖

  • 包含標頭檔案 #include <pthread.h>
  • 互斥鎖靜態初始化
    選擇以下其一即可
pthread_mutex_t fastmutex = PTHREAD_MUTEX_INITIALIZER; // 快速互斥鎖
pthread_mutex_t recmutex = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP; // 遞迴互斥鎖
pthread_mutex_t errchkmutex = PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP; // 檢錯互斥鎖
  • 快速互斥鎖:具有阻塞機制,不具備遞迴特性。
  • 遞迴互斥鎖:遞迴獲取互斥鎖時,持有互斥鎖的計數加1
  • 檢錯互斥鎖:快速互斥鎖的非阻塞版本
  • 互斥鎖動態初始化
    • mutex 初始化互斥鎖結構的指標
    • mutexattr 屬性引數,如果該引數為 NULL,則表示選擇預設配置,為快速互斥鎖。
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr);

9.3 獲取互斥鎖與釋放互斥鎖

  • 注:非遞迴互斥鎖不具備遞迴特性。
  • int pthread_mutex_lock(pthread_mutex_t *mutex); :獲取互斥鎖並上鎖,具有阻塞功能
  • int pthread_mutex_lock(pthread_mutex_t *mutex); :獲取互斥鎖並上鎖,不阻塞,發現鎖被佔用後會返回EBUSY錯誤
  • int pthread_mutex_unlock(pthread_mutex_t *mutex); :解鎖並釋放互斥鎖

9.4 銷燬互斥鎖

  • int pthread_mutex_destroy(pthread_mutex_t *mutex); :銷燬互斥鎖

參考

  * 野火

相關文章