可重入函數與線程安全的區別和聯繫

1.關於可重入函數


當捕捉到信號時,不論進程的主控制流程當前執行到哪,都會先跳到信號處理函數中執行,從信號處理函數返回後再繼續執行主控制流程。信號處理函數是一個單獨的控制流程,因爲它和主控制流程是異步的,二者不存在調用和被調用的關係,並且使用不同的堆棧空間。引入了信號處理函數使得整個進程具有多個控制流程,如果這些控制流程訪問相同的全局資源(全局變量、硬件資源等),就有可能出現衝突。

可重入函數主要用於多任務環境中,一個可重入的函數簡單來說就是可以被中斷的函數,也就是說,可以在這個函數執行的任何時刻中斷它,轉入OS調度下去執行另外一段代碼,而返回控制時不會出現什麼錯誤;而不可重入的函數由於使用了一些系統資源,比如全局變量區,中斷向量表等,所以它如果被中斷的話,可能會出現問題,這類函數是不能運行在多任務環境下的。

  可重入函數也可以這樣理解,重入即表示重複進入,首先它意味着這個函數可以被中斷,其次意味着它除了使用自己棧上的變量以外不依賴於任何環境(包括static),這樣的函數就是purecode(純代碼)可重入,可以允許有該函數的多個副本在運行,由於它們使用的是分離的棧,所以不會互相干擾。如果確實需要訪問全局變量(包括static),一定要注意實施互斥手段。可重入函數在並行運行環境中非常重要,但是一般要爲訪問全局變量付出一些性能代價。

2.線程安全

  一個函數被稱爲線程安全的(thread-safe),當且僅當被多個併發進程反覆調用時,它會一直產生正確的結果。

  比如一個 ArrayList 類,在添加一個元素的時候,它可能會有兩步來完成:1. 在 Items[Size] 的位置存放此元素;2. 增大 Size 的值。

單線程運行的情況下,如果 Size = 0,添加一個元素後,此元素在位置 0,而且 Size=1;

而如果是在多線程情況下,比如有兩個線程,線程 A 先將元素1存放在位置 0。但是此時 CPU 調度線程A暫停,線程 B 得到運行的機會。線程B向此 ArrayList 添加元素2,因爲此時 Size 仍然等於 0 (注意,我們假設的是添加一個元素是要兩個步驟,而線程A僅僅完成了步驟1),所以線程B也將元素存放在位置0。然後線程A和線程B都繼續運行,都增加 Size 的值,結果Size等於2。

那好,我們來看看 ArrayList 的情況,期望的元素應該有2個,而 實際元素是在0位置,造成丟失元素,而且Size 等於 2。這就是“線程不安全”了。

3.兩者的區別和聯繫

可重入函數是線程安全函數的一種,其特點在於它們被多個線程調用時,不會引用任何共享數據,也就是不引用靜態或全局變量。
可重入函數通常要比不可重入的線程安全函數效率高一些,因爲它們不需要同步操作。更進一步說,將第2類線程不安全函數轉化爲線程安全函數的唯一方法就是重寫它,使之可重入。

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



int length = 0;
char *s = NULL;
// Note: Since strings end with a 0, if we want to
// add a 0, we encode it as "\0", and encode a
// backslash as "\\".
// WARNING! This code is buggy - do not use!
void AddToString(int ch)
{
EnterCriticalSection(&someCriticalSection);
// +1 for the character we're about to add
// +1 for the null terminator
char *newString = realloc(s, (length+1) * sizeof(char));
if (newString) {
if (ch == '\0' || ch == '\\') {
AddToString('\\'); // escape prefix
}
newString[length++] = ch;
newString[length] = '\0';
s = newString;
}
LeaveCriticalSection(&someCriticalSection);
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章