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); }