1. 程式人生 > >對比執行緒安全和可重入函式

對比執行緒安全和可重入函式

1.什麼叫執行緒安全

如果你的程式所在的程序中有多個執行緒在同時執行,而這些執行緒可能同時執行一段程式碼或同時訪問一個物件,如果每次執行完這段程式碼或訪問完這個物件之後,所得到的結果和單執行緒執行的結果一樣,而其他變數的值也和預期的保持一致,那麼就認為是執行緒安全的。  
  也就是說當多個執行緒同時運行同一段程式碼,不會造成資源的衝突,不會產生錯誤的結果就是執行緒安全的。如果有一段執行緒安全的程式碼(原子操作或執行緒間切換不會導致結果的二義性),它在多個執行緒中使用是不需要作同步處理的;而執行緒不安全的程式碼在多執行緒環境中必須作同步處理,否則會造成不可不可預期的後果。

2.不可重入性和可重入性

  可重入函式是由於一個任務併發使用,而不用擔心資料的錯誤,相反,不可重入函式不能被一個任務所共享,除非能確保函式的互斥(使用訊號量或禁用中斷)。可重入函式可以在任意時刻被中斷,稍後再執行,資料不會產生問題。不可重入函式要麼使用區域性變數,要麼使用全域性變數時保護自己的資料(加鎖等方式)。

3.可重入函式

(1)不在函式內部使用靜態或全域性資料 (2)不返回指向靜態資料的指標,所有資料都由函式呼叫者來提供 (3)使用本地資料,或通過製作全域性資料的本地拷貝來保護全域性資料 (4)如果必須訪問全域性變數,利用互斥機制來保護全域性變數 (5)不呼叫不可重入函式

4.不可重入函式

(1)函式中使用了靜態變數,不論全域性或者區域性靜態變數
(2)函式返回靜態變數或靜態的資料結構 (3)函式體內呼叫了malloc()或者free()函式 (4)函式中呼叫了不可重入函式 (5)呼叫了標準I/O庫函式 如果一個函式在重入條件下使用了未受保護的共享資源那它是不可重入的。

5.執行緒不安全函式的分類

(1)不保護共享變數的函式採用加鎖即可。

(2)保持跨越多個呼叫的狀態函式。如rand庫函式,這種情況下要麼重寫它,使它不包含任何靜態資料,依靠呼叫者在引數中傳遞引數資訊;或者採用庫函式提供的可重入版本,而可重入版本的函式名是在原函式名尾部加上_r,即為rand_r。 (3)返回指向靜態變數指標的函式gethostbyname函式
,該函式內部用一個靜態變數儲存轉化結果,函式的返回值指向該靜態記憶體。當併發執行緒中呼叫這些函式,因為正在被一個執行緒使用的結果會被另一個執行緒悄悄地覆蓋了。一種處理方法是可以採用使用執行緒特定資料來替換靜態儲存。但是,此替換涉及到動態分配儲存,並且會增加呼叫開支。處理該問題的更好方法是呼叫方可通過例程的其他輸出引數來提供儲存。其他輸出引數需要gethostbyname() 函式的新接口。即gethostbyname_r()函式。
注:執行緒特定資料(Thread Specific Data),是儲存和查詢與某個執行緒相關的資料的一種機制。把這種資料稱為執行緒私有資料或執行緒特定資料的原因是,希望每個執行緒可以獨立地訪問資料副本,從而不需要考慮多執行緒同步問題
struct hostent* gethostbyname_ts(char* host)  
{  
    struct hostent* shared, * unsharedp;  
    unsharedp = Malloc(sizeof(struct hostent));  
    P(&mutex)  
    shared = gethostbyname(hostname);  
    *unsharedp = * shared;  
    V(&mutex);  
    return unsharedp;  
}  

(4)呼叫了執行緒不安全函式的函式。

6.執行緒安全與可重入函式的對比

(1)可重入函式一定是執行緒安全函式 (2)執行緒安全函式不一定是可重入函式(有可能通過互斥機制實現執行緒安全不安全函式—>執行緒安全函式的轉換等) (3)可重入性要強於執行緒安全性(相當於函式產生相同結果時可重入性的條件要苛刻)