1. 程式人生 > >ZZ可重入與非同步訊號安全

ZZ可重入與非同步訊號安全

可重入與非同步訊號安全

From:http://blog.chinaunix.net/u/12592/showart_1871048.html

一個可重入的函式簡單來說就是可以被中斷的函式,也就是說,可以在這個函式執行的任何時刻中斷它,轉入OS排程下去執行另外一段程式碼,而返回控制時不會出現什麼錯誤。
《多執行緒程式設計指南》中定義,可以被訊號控制器安全呼叫的函式被稱為"非同步訊號安全"函式。
因此,我認為可重入與非同步訊號安全是一個概念

有人將可重入函式與執行緒安全函式混為一談,我認為是不正確的。

這裡引用CSAPP中的描述來說明一下:
--------------------------------------------------


CSAPP
13.7.1 執行緒安全
一個函式被稱為執行緒安全的,當且僅當被多個併發執行緒反覆的呼叫時,它會一直產生正確的結果。
13.7.2 可重入性
有一類重要的執行緒安全函式,叫做可重入函式,其特點在於它們具有一種屬性:當它們被多個執行緒呼叫時,不會引用任何共享的資料。

儘管執行緒安全和可重入有時會(不正確的)被用做同義詞,但是它們之間還是有清晰的技術差別的。可重入函式是執行緒安全函式的一個真子集。
--------------------------------------------------

重入即表示重複進入,首先它意味著這個函式可以被中斷,其次意味著它除了使用自己棧上的變數以外不依賴於任何環境(包括static),這樣的函式就是purecode(純程式碼)可重入,可以允許有該函式的多個副本在執行,由於它們使用的是分離的棧,所以不會互相干擾。
可重入函式是執行緒安全函式,但是反過來,執行緒安全函式未必是可重入函式。
實際上,可重入函式很少,APUE 10.6節中描述了Single UNIX Specification說明的可重入的函式,只有115個;APUE 12.5節中描述了POSIX.1中不能保證執行緒安全的函式,只有89個。

信 號就像硬體中斷一樣,會打斷正在執行的指令序列。訊號處理函式無法判斷捕獲到訊號的時候,程序在何處執行。如果訊號處理函式中的操作與打斷的函式的操作相 同,而且這個操作中有靜態資料結構等,當訊號處理函式返回的時候(當然這裡討論的是訊號處理函式可以返回),恢復原先的執行序列,可能會導致訊號處理函式 中的操作覆蓋了之前正常操作中的資料。
不可重入函式的原因在於:
1> 已知它們使用靜態資料結構
2> 它們呼叫malloc和free.
因為malloc通常會為所分配的儲存區維護一個連結表,而插入執行訊號處理函式的時候,程序可能正在修改此連結表。
3> 它們是標準IO函式.
因為標準IO庫的很多實現都使用了全域性資料結構

即 使對於可重入函式,在訊號處理函式中使用也需要注意一個問題就是errno。一個執行緒中只有一個errno變數,訊號處理函式中使用的可重入函式也有可能 會修改errno。例如,read函式是可重入的,但是它也有可能會修改errno。因此,正確的做法是在訊號處理函式開始,先儲存errno;在訊號處 理函式退出的時候,再恢復errno。

例如,程式正在呼叫printf輸出,但是在呼叫printf時,出現了訊號,對應的訊號處理函式也有printf語句,就會導致兩個printf的輸出混雜在一起。
如果是給printf加鎖的話,同樣是上面的情況就會導致死鎖。對於這種情況,採用的方法一般是在特定的區域遮蔽一定的訊號。
遮蔽訊號的方法:
1> signal(SIGPIPE, SIG_IGN); //忽略一些訊號
2> sigprocmask()
sigprocmask只為單執行緒定義的
3> pthread_sigmask()
pthread_sigmasks可以在多執行緒中使用

現在看來訊號非同步安全和可重入的限制似乎是一樣的,所以這裡把它們等同看待;-)


執行緒安全
執行緒安全:如果一個函式在同一時刻可以被多個執行緒安全的呼叫,就稱該函式是執行緒安全的。

不需要共享時,請為每個執行緒提供一個專用的資料副本。如果共享非常重要,則提供顯式同步,以確保程式以確定的方式操作。通過將過程包含在語句中來鎖定和解除鎖定互斥,可以使不安全過程變成執行緒安全過程,而且可以進行序列化。

很多函式並不是執行緒安全的,因為他們返回的資料是存放在靜態的記憶體緩衝區中的。通過修改介面,由呼叫者自行提供緩衝區就可以使這些函式變為執行緒安全的。
作業系統實現支援執行緒安全函式的時候,會對POSIX.1中的一些非執行緒安全的函式提供一些可替換的執行緒安全版本。
例如,gethostbyname()是執行緒不安全的,在Linux中提供了gethostbyname_r()的執行緒安全實現。
函式名字後面加上"_r",以表明這個版本是可重入的(對於執行緒可重入,也就是說是執行緒安全的,但並不是說對於訊號處理函式也是可重入的,或者是非同步訊號安全的)。

多執行緒程式中常見的疏忽性問題
1> 將指標作為新執行緒的引數傳遞給呼叫方棧。
2> 在沒有同步機制保護的情況下訪問全域性記憶體的共享可更改狀態。
3> 兩個執行緒嘗試輪流獲取對同一對全域性資源的許可權時導致死鎖。其中一個執行緒控制第一種資源,另一個執行緒控制第二種資源。其中一個執行緒放棄之前,任何一個執行緒都無法繼續
操作。
4> 嘗試重新獲取已持有的鎖(遞迴死鎖)。
5> 在同步保護中建立隱藏的間隔。如果受保護的程式碼段包含的函式釋放了同步機制,而又在返回呼叫方之前重新獲取了該同步機制,則將在保護中出現此間隔。結果具有誤導性。對於呼叫方,表面上看全域性資料已受到保護,而實際上未受到保護。
6> 將UNIX 訊號與執行緒混合時,使用sigwait(2) 模型來處理非同步訊號。
7> 呼叫setjmp(3C) 和longjmp(3C),然後長時間跳躍,而不釋放互斥鎖。
8> 從對*_cond_wait() 或*_cond_timedwait() 的呼叫中返回後無法重新評估條件。


總結

判斷一個函式是不是可重入函式,在於判斷其能否可以被打斷,打斷後恢復執行能夠得到正確的結果。(打斷執行的指令序列並不改變函式的資料)
判斷一個函式是不是執行緒安全的,在於判斷其能否在多個執行緒同時執行其指令序列的時候,保證每個執行緒都能夠得到正確的結果。

如果一個函式對多個執行緒來說是可重入的,則說這個函式是執行緒安全的,但這並不能說明對訊號處理程式來說該函式也是可重入的。
如果函式對非同步訊號處理程式的重入是安全的,那麼就可以說函式是"非同步-訊號安全"的。

參考:
CSAPP
13.7.1 執行緒安全
13.7.2 可重入性
《Advanced Programming in the UNIX Environment》2nd Editon
10.6 Reentrant Function
12.5 Reentrant
《多執行緒程式設計指南》
訊號處理程式和非同步訊號安全
http://blog.chinaunix.net/u/25994/showart_369466.html