非同步可重入函數與線程安全函數等價嗎?

apue的信號和多線程兩章都提到了函數重入性,在非同步信號下可重入的函數叫非同步可重入,在多線程下可重入叫線程安全。那麼這兩種情況下的可重入函數是否等價,如果不等價哪個更嚴格?


在Unix/Linux系統中,signal是以軟中斷的方式分發的,signal handler可能在任何時候打斷一個進程的任意一個線程而執行(如果該線程沒有屏蔽該signal的話)。

比如,pthread_mutex_lock函數顯然是線程安全的,但是它不是非同步可重入的,考慮下面的情況,有這麼一段代碼:

pthread_mutex_lock(gLock);

change_some_thing();

pthread_mutex_unlock(gLock);

假定有一個工作線程A運行到這段代碼,調用了pthread_mutex_lock但是還沒返回的時候,有個signal產生了,signal handler打斷線程A執行,然後在signal handler的上下文中也運行到了這段代碼(注意signal handler其實借用了線程A的棧執行代碼,這一點很像操作系統內核的中斷處理),signal handler調用pthread_mutex_lock的時候,線程A的pthread_mutex_lock可能才執行了一半,因為pthread_mutex_lock不是非同步可重入的,所以在signal handler的上下文中的pthread_mutex_lock調用很可能會破壞pthread_mutex_t的內部狀態,導致程序死鎖等異常行為。

類似的情況還有很多,最後導致的結果也不盡相同。

不過非同步可重入應該是Unix/Linux這種支持signal的系統特有的。在Windows下,其實並不存在類似的問題,Windows的C Runtime雖然也有signal這樣的函數,但是它更像是為了保持向前兼容而做的模擬,因為Windows的signal是通過特定的線程分發的,所以它不會打斷應用的線程。所有線程安全的函數在Windows的signal handler中應該都可以使用。在Windows中,SEH機制更接近Unix/Linux中的signal handler,但是顯然SEH更可控一點。

個人並不喜歡Unix/Linux中的signal機制,Unix/Linux中的signal機制更像是一種用戶態的中斷,當它和多線程機制同時出現的時候,總是顯得格格不入。


Linux 中可重入這個概念一般只有在 signal 的場景下有意義,叫 async-signal-safe。

很多線程安全的函數都是不可重入的,例如 malloc。

可重入的函數一般也是線程安全的,雖然據說有反例,但我沒見過。

Posix中大多數函數都是線程安全的,但只有少數是 async-signal-safe。

見《Linux多線程服務端編程》第4.2節和第4.10節。


樓主的概念有點混亂,當然主要是中文翻譯後一個概念往往對於很多概念,讓人迷惑。

信號可重入: 雖然有的地方確實E文是非同步信號重入,但我們就重點還是放在信號上把。雖然信號也是非同步的一個體系,但現在幾乎沒人用信號做非同步,頂多也就處理一些Ctrl +C等信號處理。為什麼不建議大家用這玩意,就是詭異的可重入問題,簡單說就是發生信號了,跳到信號處理函數處理了,回來的點那個函數能否繼續下去。悲催很多函數不可重入,不能回到原來那個點怎麼寫代碼?是很多時候沒法寫。所以大家也不用這玩意。

線程安全:線程安全是在多線程環境下一個函數不會出現奇怪的問題,比如localtime這個函數。他返回的是一個tm的指針。其實是自己內部的static 變數的指針。而兩個線程同時調用的時候,這個static變數保存的值可能就會被後面使用那個線程改寫,讓前面那個線程獲得錯誤的值。所以他就不是線程安全的。線程的出現沒有那麼早。而C語言誕生更早一些,也就別責怪API的設計者了。後面為了解決這個問題,又增加了一組_r結尾的函數。比如locatime_r。

非同步可重入:其實沒有太明確的這個概念,我解釋一下非同步中的上下文失效問題。但我們傳統的非同步概念中,無論是FSM還是協程,包括多線程的同步轉非同步,在可重入問題上沒有太好的解決。舉一個例子,你一個在一個線程裡面處理用戶X數據一個多步處理,線程保存了用戶X的指針,等待伺服器A上處理返回結果,這時線程在recv上等待。等結果返回時,這是A返回了結果。線程繼續下去,但這是你可能還要檢查X的指針有效性,因為這個時間內可能發生什麼導致X的指針失效了(比如X下線了)。


兩者不等價。可重入更嚴格。

如果一個函數的實現使用了全局或者靜態變數,那麼這個函數既不是可重入的,也不是線程安全的。

如果放寬條件,這個函數仍然用到了全局或者靜態變數,但是在訪問這些變數時,通過加鎖來保證互斥訪問,那麼這個函數就可以變成線程安全的函數。但它此時仍然是不可重入的,因為通常加鎖是針對不同線程的訪問,對同一線程可能出現問題(發生信號軟中斷,signal handler中恰巧也執行了該函數)。

那麼如果把函數中的全局和靜態變數都幹掉,並保證在該函數中也不調用不可重入的函數,那麼這個函數可以做到既是線程安全的,也是可重入的。

綜上,可重入函數一般都是線程安全的,線程安全的不一定是可重入的。


可重入函數是線程安全函數的子集。

可重入的要求是:不使用、返回任何非常量的 全局|靜態 變數,也不調用任何不可重入的函數。

線程安全的要求是:在多個線程同時調用一個函數時,能夠得到預期的結果。

線程安全函數,在執行時,多個線程可以互相影響,即使輸入一樣,輸出也可能因為函數中途其他線程的行為而改變,所以函數返回值不具有可再現性(輸入輸出一一對應),也不一定是可重入函數了。


非同步可重入跟線程安全不是一回事,雖然有時候兩者同時滿足。

樓上有人指出嚴格的可重入應該滿足線程安全,這也許是實際中常見的情形。另外@vczh 說線程安全滿足事務要求似乎也嚴格了些。

@董俊傑 提到可重入部分不能掛起也是現實中常見要求。

為了一致性避免所有這些問題,選擇lockfree的數據結構是發展趨勢。


自己頂下,拋磚引玉。

我現在是覺得非同步可重入要求更高些,線程安全可以用鎖,非同步可重入不行。

期待高手解答。


線程安全講的是關於transaction的問題,比非同步要求要嚴格多了


深入理解可重入與線程安全,看完這篇就比較清楚了。

補充一篇,線程安全、異常安全、可重入 - shuaihanhungry - 博客園


可重入與線程安全兩者概念雖然表面上看有些許交集,但實際上完全是在描述函數的兩個不同的特性,完全可以理解為兩個截然不同的概念。

所謂函數重入是指一個函數在執行完畢前由於某種原因被中斷,在中斷處理中該函數被再次調用。所謂函數可重入是指函數在不被上述中斷影響下的執行結果與出現上述中斷情況下的最終執行結果一致。

所謂線程安全函數在某種程度上可以簡單理解為n個線程同時執行該函數,全部線程執行完畢後的結果與單個線程執行該函數n次完畢後結果一致。

既然是兩個截然不同的概念,他們之間其實並沒有什麼必然的聯繫。可重入不一定線程安全,線程安全也不一定可重入。


推薦閱讀:

大括弧不換行的壞處有什麼?為什麼有人不換行?
C語言內存中是否存在一個區域,存儲著變數的符號,變數的類型和變數的首地址?
c語言為什麼可以通過變數名來訪問變數的值?變數的值是存儲在計算機中,那麼變數名也同時存儲在計算機中嗎?
C 語言有什麼奇技淫巧?
如何快速便捷地打小括弧?

TAG:編程 | C編程語言 | Unix | 計算機科學 |