可衝入函式與多執行緒安全函式

2021-05-23 09:46:47 字數 4011 閱讀 2734

1. reentrant函式

乙個函式是reentrant的,如果它可以被安全地遞迴或並行呼叫。要想成為reentrant式的函式,該函式不能含有(或使用)靜態(或全域性)資料(來儲存函式呼叫過程中的狀態資訊),也不能返回指向靜態資料的指標,它只能使用由呼叫者提供的資料,當然也不能呼叫non-reentrant函式.

比較典型的non-reentrant函式有getpwnam, strtok, malloc等.

reentrant和non-reentrant函式的例子

#include

#include

#include

#include

#include

#include

#include

int* getpower(int i)

void getpower_r(int i, int* result)

void handler (int signal_number)        /*處理sigalrm訊號*/

int main ()

複製**

試驗方法:

1. 編譯 gcc test.c -lpthread

在乙個終端中執行 ./a.out, 在另乙個終端中執行 ps -a|grep a.out可以看到該程序的id

2. 用如下方式執行a.out:

執行./a.out,在按回車前,在另外乙個終端中執行kill -14 pid (這裡的pid是執行上面的ps時看到的值)

然後,按回車繼續執行a.out就會看到2^5 = 8 的錯誤結論

對於函式int* getpower(int i)

由於函式getpower會返回乙個指向靜態資料的指標,在第一次呼叫getpower的過程中,再次呼叫getpower,則兩次返回的指標都指向同一塊

記憶體,第二次的結果將第一次的覆蓋了(很多non-reentrant函式的這種用法會導致不確定的後果).所以是non-reentrant的.

對於函式void getpower_r(int i, int* result)

getpower_r會將所得的資訊儲存到result所指的記憶體中,它只是使用了由呼叫者提供的資料,所以是reentrant.在訊號處理函式中可以正常的使用它.

2. thread-safe函式

thread safety是多執行緒程式設計中的概念,thread safe函式是指那些能夠被多個執行緒同時併發地正確執行的函式.

thread safe和non thread safe的例子

#include

#include

#include

pthread_mutex_t sharedmutex=pthread_mutex_initializer;

int count;        /*共享資料*/

void* func (void* unused)

void* func_s (void* unused)

int main ()

複製**

函式func是non thread safe的,這是因為它不能避免對共享資料count的race condition,

設想這種情況:一開始count是0,當執行緒1進入func函式,判斷過count == 0後,執行緒2進入func函式

執行緒2判斷count==0,並執行count++,然後執行緒1開始執行,此時count != 0 了,但是執行緒1仍然要執行

count++,這就產生了錯誤.

func_s通過mutex鎖將對共享資料的訪問鎖定,從而避免了上述情況的發生.func_s是thread safe的

只要通過適當的"鎖"機制,thread safe函式還是比較好實現的.

3. reentrant函式與thread safe函式的區別

reentrant函式與是不是多執行緒無關,如果是reentrant函式,那麼要求即使是同乙個程序(或執行緒)同時多次進入該函式時,該函式仍能夠正確的運作.

該要求還蘊含著,如果是在多執行緒環境中,不同的兩個執行緒同時進入該函式時,該函式也能夠正確的運作.

thread safe函式是與多執行緒有關的,它只是要求不同的兩個執行緒同時對該函式的呼叫在邏輯上是正確的.

從上面的說明可以看出,reentrant的要求比thread safe的要求更加嚴格.reentrant的函式必是thread safe的,而thread safe的函式

未必是reentrant的. 舉例說明:

#include

#include

#include

#include

#include

#include

#include

pthread_mutex_t sharedmutex=pthread_mutex_initializer;

int count;        /*共享資料*/

void* func_s (void* unused)

void handler (int signal_number)        /*處理sigalrm訊號*/

int main ()

複製**

試驗方法:

1. 編譯 gcc test.c -lpthread

在乙個終端中執行 ./a.out, 在另乙個終端中執行 ps -a|grep a.out可以看到該程序的id

2. 進行下面4次執行a.out:

每次執行分別在第1,2,3,4次回車前,在另外乙個終端中執行kill -14 pid (這裡的pid是上面ps中看到的值)

試驗結果:

1. 該程序中有3個執行緒:乙個主線程,兩個子執行緒

2. func_s是thread safe的

3. func_s不是reentrant的

4. 訊號處理程式會中斷主線程的執行,不會中斷子執行緒的執行

5. 在第1,4次回車前,在另外乙個終端中執行kill -14 pid會形成死鎖,這是因為

主線程先鎖住了臨界區,主線程被中斷後,執行handler(以主線程執行),handler試圖鎖定臨界區時,

由於同乙個執行緒鎖定兩次,所以形成死鎖

6. 在第2,3次回車前,在另外乙個終端中執行kill -14 pid不會形成死鎖,這是因為乙個子執行緒先鎖住

了臨界區,主線程被中斷後,執行handler(以主線程執行),handler試圖鎖定臨界區時,被掛起,這時,子執行緒

可以被繼續執行.當該子執行緒釋放掉鎖以後,handler和另外乙個子執行緒可以競爭進入臨界區,然後繼續執行.

所以不會形成死鎖.

結論:

1. reentrant是對函式相當嚴格的要求,絕大部分函式都不是reentrant的(apue上有乙個reentrant函式

的列表).

什麼時候我們需要reentrant函式呢?只有乙個函式需要在同乙個執行緒中需要進入兩次以上,我們才需要

reentrant函式.這些情況主要是非同步訊號處理,遞迴函式等等.(non-reentrant的遞迴函式也不一定會

出錯,出不出錯取決於你怎麼定義和使用該函式). 大部分時候,我們並不需要函式是reentrant的.

2. 在多執行緒環境當中,只要求多個執行緒可以同時呼叫乙個函式時,該函式只要是thread safe的就可以了.

我們常見的大部分函式都是thread safe的,不確定的話請查閱相關文件.

3. reentrant和thread safe的本質的區別就在於,reentrant函式要求即使在同乙個執行緒中任意地進入兩次以上,

也能正確執行.

大家常用的malloc函式是乙個典型的non-reentrant但是是thread safe函式,這就說明,我們可以方便的

在多個執行緒中同時呼叫malloc,但是,如果將malloc函式放入訊號處理函式中去,這是一件很危險的事情.

4. reentrant函式肯定是thread safe函式,也就是說,non thread safe肯定是non-reentrant函式

不能簡單的通過加鎖,來使得non-reentrant函式變成 reentrant函式

執行緒安全與可重入函式

可重入函式 reentrant function 與執行緒安全函式 thread safe function 有時容易混淆,而且各種文件中的解釋也不是很清楚,這裡根據筆者的經驗來說明一下。執行緒安全函式 概念 執行緒安全的概念比較直觀。一般說來,乙個函式被稱為執行緒安全的,當且僅當被多個併發執行緒反...

可重入函式與執行緒安全

執行緒安全 乙個函式被稱為執行緒安全的 thread safe 當且僅當被多個併發程序反覆呼叫時,它會一直產生正確的結果。如果乙個函式不是執行緒安全的,我們就說它是執行緒不安全的 thread unsafe 我們定義四類 有相交的 執行緒不安全函式。將這類執行緒不安全函式變為執行緒安全的,相對比較容...

可重入函式與執行緒安全

可重入函式與執行緒安全 執行緒安全 假如在乙個函式中它是這麼寫的,在乙個全域性鍊錶上存放資料,在單執行緒模式下,我們先new乙個新的節點然後讓head next指向這個節點,這種場景在多執行緒場景下會是這樣的過程,執行緒一new了乙個節點,然後cpu轉去執行執行緒二,執行緒二new乙個節點後head...