本文轉自:http://zh.wikipedia.org/wiki/%E5%8F%AF%E9%87%8D%E5%85%A5
若一個程式或子程式可以“安全的被並存執行(Parallel
computing)”,則稱其為可重新進入(reentrant或re-entrant)的。即當該子程式正在運行時,可以再次進入並執行它(並存執行時,個別的執行結果,都符合設計時的預期)。可重新進入概念是在單線程作業系統的時代提出的。一個子程式的重入,可能由於自身原因,如執行了jmp或者call,類似於子程式的遞迴調用;或者由於硬體中斷,UNIX系統的signal的處理,即子程式被中斷處理常式或者signal處理常式調用。重入的子程式,按照後進先出線性序依次執行。
若一個函數是可重新進入的,則該函數:
- 不能含有靜態(全域)非常量資料。
- 不能返回靜態(全域)非常量資料的地址。
- 只能處理由調用者提供的資料。
- 不能依賴於單一實例模式資源的鎖。
- 不能調用(call)不可重新進入的函數(有呼叫(call)到的函數需滿足前述條件)。
多“使用者/對象/進程優先順序”以及多進程,一般會使得對可重新進入代碼的控制變得複雜。同時,IO代碼通常不是可重新進入的,因為他們依賴於像磁碟這樣共用的、單獨的(類似編程中的靜態(Static)、全域(Global))資源。
可重新進入性是函數程式設計語言的關鍵特性之一。
例子
在以下的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的時間片,不斷向前推進執行進度。可重新進入函數未必是安全執行緒的;安全執行緒函數未必是可重新進入的。例如,一個函數開啟某個檔案並讀入資料。這個函數是可重新進入的,因為它的多個執行個體同時執行不會造成衝突;但它不是安全執行緒的,因為在它讀入檔案時可能有別的線程正在修改該檔案,為了安全執行緒必須對檔案加“同步鎖”。另一個例子,函數在它的函數體內部訪問共用資源使用了加鎖、解鎖操作,所以它是安全執行緒的,但是卻不可重新進入。因為若該函數一個執行個體運行到已經執行加鎖但未執行解鎖時被停下來,系統又啟動該函數的另外一個執行個體,則新的執行個體在加鎖處將轉入等待。如果該函數是一個中斷處理服務,在中斷處理時又發生新的中斷將導致資源死結。
下述例子,是安全執行緒的,但不是可重新進入的。
int function(){ mutex_lock(); ... function body ... mutex_unlock();}
多線程執行時,獲得了互斥鎖的線程總能獲得CPU時間片,向前推進執行進度,最終解開互斥鎖,使得別的線程也能獲得互斥鎖進入臨界區。但是,如果在單線程背景下第一次執行該函數時已經獲得互斥鎖進入臨界區,這時該函數被重入執行,這將在重新申請互斥鎖時被餓死(starvation),因為獲得了互斥鎖的該函數的第一次執行將永遠沒有機會再獲得CPU時間片。