sem_init重復調用引發sem_wait線程無法被喚醒


問題

一段老代碼,兩個線程,一個線程調用sem_wait等待信號量,另外一個線程在某失敗分支會調用sem_init清信號量,結果導致sem_wait線程無法被喚醒;

分析

Linux manpage

從描述中可見,初始化一個已經被初始化的信號量會導致未定義行為;

 1 NAME
 2        sem_init - initialize an unnamed semaphore
 3 
 4 SYNOPSIS
 5        #include <semaphore.h>
 6 
 7        int sem_init(sem_t *sem, int pshared, unsigned int value);
 8 
 9        Link with -lrt or -pthread.
10 
11 DESCRIPTION
12 ...
13 
14        Initializing a semaphore that has already been initialized results in undefined behavior.

glibc源碼

到底會發生什么未定義行為,我們直接看源碼吧;

首先,對比結構體,舊結構體只有value成員,新結構體中增加了private和nwaiters成員;nwaiters成員會在調用sem_wait時候增加;

然后,對比sem_post喚醒函數;可見,新喚醒函數會在喚醒操作執行之前對nwaiters進行判斷,只有當nwaiters>0時,才進行喚醒;

而舊的喚醒操作,則沒有類似判斷;

現在,我們清楚了,老代碼用的老版本的glibc,內部沒有等待判斷,一直沒有出問題,而使用新版本的glibc之后,加入了判斷,就有問題了;

結論

  1. sem_init是用來在初始化的時候調用初始化信號量的,並不是用來將信號量清零的;
  2. 重復調用sem_init的行為可能導致已經處於sem_wait的線程無法被喚醒;
  3. 舊版本的glibc機制比較弱,所以老代碼一直運行很好;但是新glibc作了檢查,所以會出問題;
  4. 按照目前代碼看,如果單個線程自己在調用了sem_wait之后再調用sem_init時沒什么影響的;但是不保證以后的glibc會再做什么修改造成影響;
  5. 除了初始化階段,其他流程中不要使用sem_init;
  6. 最好使用其他方式替代信號量,比如條件變量;


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM