可重入和線程安全

   線程安全:
       線程安全函數:在C語言中局部變量是在棧中分配的,任何未使用靜態數據或其他共享資源的函數都是線程安全的。
                     使用全局變量的函數是非線程安全的。
                     使用靜態數據或其他共享資源的函數,必須通過加鎖的方式來使函數實現線程安全。

       線程安全的(Thread-Safe):
                   如果一個函數在同一時刻可以被多個線程安全地調用,就稱該函數是線程安全的。
                   線程安全函數解決多個線程調用函數時訪問共享資源的衝突問題。

       可重入(Reentrant):
                   函數可以由多於一個線程併發使用,而不必擔心數據錯誤。可重入函數可以在任意時刻被中斷,稍後再繼續運行,不會丟失數據。可重  入性解決函數運行結果的確定性和可重複性。


可重入函數編寫規範爲:

1、不在函數內部使用靜態或全局數據 
2、不返回靜態或全局數據,所有數據都由函數的調用者提供。 
3、使用本地數據,或者通過製作全局數據的本地拷貝來保護全局數據。
4、如果必須訪問全局變量,利用互斥機制來保護全局變量。
5、不調用不可重入函數。

兩者之間的關係:
1、一個函數對於多個線程是可重入的,則這個函數是線程安全的。
2、一個函數是線程安全的,但並不一定是可重入的。【使用互斥鎖實現的線程安全】
3、可重入性要強於線程安全性。

比如:
     strtok函數是既不可重入的,也不是線程安全的。加鎖的strtok不是可重入的,但線程安全。
     而strtok_r既是可重入的,也是線程安全的。(具體可以查看man手冊) 

_____________________________________________________________


爲了寫一個穩定的多線程程序,必須遵守線程安全,但不一定遵守可重入。 
    安全是指多個線程調用同一個函數,如果是線程安全的,那麼每次的結果都是正確。

    可重入函數是指函數內部沒有使用共享變量。
    可重入函數是線程安全函數的一個真子集。

    也就是說如果函數是可重入的,就可以保證它是線程安全的,當然有些不可重入的函數也是線程安全的,比如:【系統庫函數實現的都是線程安全的。】
    系統的設計中,經常會出現多個任務調用同一個函數的情況。如果這個函數不幸被設計成爲不可重入的函數的話,那麼不同任務調用這個函數時可能修改

其他任務調用這個函數的數據,從而導致不可預料的後果。那麼什麼是可重入函數呢?

    所謂可重入函數是指一個可以被多個任務調用的過程,任務在調用時不必擔心數據是否會出錯。不可重入函數在實時系統設計中被視爲不安全函數。 int

global=0;
int foo1()
{
    return global++;
}
 

如果多個線程同時調用這個函數,每個線程返回的結果是什麼?

很明顯這個結果很難回答。因爲你不知道他們這些線程是怎麼工作的,誰先誰後都不知道,有的程序在線程的調度上只是簡單的時間片輪轉的策略,按找創建

線程的順序應該可以得到正確答案,但是如果調度策略對修改了呢,比如是實時調度。

很明顯一個可重入的函數的很必要的。

你或許想難道這個可重入函數不能使用全局變量了嗎,你真正需要的或許只是線程安全,沒有必要可重入,那麼你可以這樣改

int global=0;
int foo1()
{
    int local=global;
    return local++;
}
 

 

這就是爲什麼你或許看到很多程序都要在開始用一個局部變量保存一個全局變量的原因。

第二種情況 果函數中使用了靜態變量或者返回靜態變量或者靜態數據結構,靜態變量包括全局靜態變量或者局部靜態變量,函數也有是不可重入的,例如

int foo2()
{
    static int a=0;
    return a++;
}
 

要使它變得線程安全,其實很簡單,在使用它之前,保存它的值。或者另一種情況這些變量都是隻讀的。

第三種情況 數中使用了malloc或者free函數,天呢,線程沒有動態內存分配?不是的,malloc和free是不可重入的,但是是線程安全的,也就是開始所說的

概念,所以你大可以繼續malloc和free。

第四種情況 函數體內調用了其他標準I/O函數

第五種情況 函數是singleton中的成員函數而且使用了不使用線程獨立存儲的成員變量 。

第六種情況 函數調用了不可衝入函數

下面一個函數就是線程安全但卻不可重入的

static int *sharedID;
int* threadsafe_getID( char* name )
{
int *unsharedID;
P( &mutex );
sharedID = notThreadsafe_getID( name );
unsharedID = sharedID;
V( & mutex);
return unsharedID;
}

 

上面的函數通過P、V操作封裝得到一個線程安全函數,卻不是可重入函數。

發佈了5 篇原創文章 · 獲贊 5 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章