跳至內容

可重入

本頁使用了標題或全文手工轉換
維基百科,自由的百科全書

若一個程式子程式可以「在任意時刻被中斷然後作業系統調度執行另一段程式碼,這段程式碼又使用了該副程式不會出錯」,則稱其為可重入(reentrant 或 re-entrant)的。即當該副程式正在運作時,執行執行緒可以再次進入並執行它,仍然可得到符合設計時所預期的結果。與多執行緒併發執行的執行緒安全不同,可重入強調對單一執行緒執行時重新進入同一個子程式仍然是安全的。

可重入概念是在單執行緒作業系統的時代提出的。一個子程式的重入,可能由於自身原因,如執行了jmp或者call,類似於子程式的遞迴呼叫;或者由於作業系統的中斷回應。UNIX系統的signal的處理,即子程式被中斷處理程式或者signal處理程式呼叫。所以,可重入也可稱作「非同步訊號安全」。這裡的非同步是指訊號中斷可發生在任意時刻。 重入的子程式,按照後進先出線性序依次執行。

若一個函式是可重入的,則該函式應當滿足下述條件:

  • 不能含有靜態(全域)非常數資料。
  • 不能返回靜態(全域)非常數資料的位址。
  • 只能處理由呼叫者提供的資料。
  • 不能依賴於單例模式資源的鎖。
  • 呼叫(call)的函式也必需是可重入的。

上述條件就是要求可重入函式使用的所有變數都儲存在呼叫堆疊的目前函式棧(frame)上,因此同一執行執行緒重入執行該函式時載入了新的函式訊框,與前一次執行該函式時使用的函式訊框不衝突、不互相覆蓋,從而保證了可重入執行安全。

多「使用者/對象/行程優先級」以及多元處理(Multiple processes),一般會使得對可重入代碼的控制變得複雜。同時,IO代碼通常不是可重入的,因為他們依賴於像磁碟這樣共享的、單獨的(類似編程中的靜態全域)資源。

可重入性是函數式程式設計的關鍵特性之一。

例子

[編輯]

在以下的C語言代碼中,函式f和函式g都不是可重入的。

 int g_var = 1;
 
 int f()
 {
   g_var = g_var + 2;
   return g_var;
 }
 
 int g()
 {
   return f() + 2;
 }

以上代碼中,f使用了全域變數 g_var,所以,如果兩個執行緒同時執行它並訪問g_var,則返回的結果取決於執行的時間。因此,f不可重入。而g呼叫了f,所以它也不可重入。

稍作修改後,兩個函式都是可重入的:

 int f(int i)
 {
   return i + 2;
 }
 
 int g(int i)
 {
   return f(i) + 2;
 }

與執行緒安全的關係

[編輯]

可重入與執行緒安全兩個概念都關係到函式處理資源的方式。但是,他們有重大區別

  • 可重入概念會影響函式的外部介面,而執行緒安全只關心函式的實現。
    • 大多數情況下,要將不可重入函式改為可重入的,需要修改函式介面,使得所有的資料都通過函式的呼叫者提供。
    • 要將非執行緒安全的函式改為執行緒安全的,則只需要修改函式的實現部分。一般通過加入同步機制以保護共享的資源,使之不會被幾個執行緒同時訪問。
  • 作業系統背景與CPU排程策略:
    • 可重入是在單執行緒作業系統背景下,重入的函式或者子程式,按照後進先出的線性序依次執行完畢。
    • 多執行緒執行的函式或子程式,各個執行緒的執行時機是由作業系統排程,不可預期的,但是該函式的每個執行執行緒都會不時的獲得CPU的時間片,不斷向前推進執行進度。
  • 可重入函式未必是執行緒安全的;執行緒安全函式未必是可重入的。
    • 例如,一個函式打開某個檔案並讀入資料。這個函式是可重入的,因為它的多個實例同時執行不會造成衝突;但它不是執行緒安全的,因為在它讀入檔案時可能有別的執行緒正在修改該檔案,為了執行緒安全必須對檔案加「同步鎖」。
    • 另一個例子,函式在它的函式體內部訪問共享資源使用了加鎖、解鎖操作,所以它是執行緒安全的,但是卻不可重入。因為若該函式一個實例執行到已經執行加鎖但未執行解鎖時被停下來,系統又啟動該函式的另外一個實例,則新的實例在加鎖處將轉入等待。如果該函式是一個中斷處理服務,在中斷處理時又發生新的中斷將導致資源死結。fprintf函式就是執行緒安全但不可重入。

下述例子,是執行緒安全的,但不是可重入的。

int function()
{
 mutex_lock();
 ...
 function body
 ...
 mutex_unlock();
}

多執行緒執行時,獲得了互斥鎖的執行緒總能獲得CPU時間片,向前推進執行進度,最終解開互斥鎖,使得別的執行緒也能獲得互斥鎖進入臨界區。但是,如果在單執行緒背景下第一次執行該函式時已經獲得互斥鎖進入臨界區,這時該函式被重入執行,這將在重新申請互斥鎖時被餓死(starvation),因為獲得了互斥鎖的該函式的第一次執行將永遠沒有機會再獲得CPU時間片。

參見

[編輯]

外部連結

[編輯]