根據我的經驗,linux下使用pthread庫寫多線程程式時,在調用系統調用/庫函數方面,應注意至少如下幾點:
1、建立了線程後,不要再使用fork()/vfork()建立子進程
2、盡量不使用signal機制
3、...
關於1,有個具體的教訓。我實現了一個動態庫,該庫的功能在一個獨立線程裡運行。同事A實現了另一個動態庫,庫的功能在也一個獨立線程裡運行。這兩個庫需要訪問一裝置,該裝置只支援一個使用者訪問,因此有個全域的訊號量,來保證這兩個線程能交替的訪問該裝置。我們的測試代碼錶明這兩個庫可以正常的配合工作。然後這兩個庫被交給同事B,整合到他的應用程式裡。問題出現了,只要我和A的線程同時運行,幾乎必然出現同時訪問裝置的錯誤情況,似乎用來保護的訊號量失敗了。多次檢查庫的代碼,沒有發現問題。偶然間,發現同事B的代碼調用了system(),把system()調用替換成相應的代碼實現,問題消失。原因在於system()實際上調用了fork()/vfork()。
為什麼在多線程調用fork會出現問題呢。我分析是因為fork會把當前進程複製到子進程,子進程也是多線程。注意在子進程執行fork()-exec()這段時間,子進程的其他線程也在運行,同時訊號量也被複製到子進程中。因為父子進程有獨立的訊號量來保護裝置,這樣父進程訪問裝置的時候,子進程也可以訪問裝置,於是出現問題。
linux下建立子進程和多線程衝突的根本原因在於fork會複製當前進程,而Windows建立子進程就沒有這個問題。為什麼會這樣呢。個人認為可能是曆史原因造成的。早期的unix注意是作為伺服器使用,例如web/ftp服務等。在沒有多線程支援的年代,伺服器支援多使用者訪問支援的方法就是為每個使用者建立一個服務子進程。服務子進程通常就是伺服器本身,因此提供一個fork調用,是可以提高效率的。但是當初設計fork調用的時候,並沒有考慮到多線程,導致與多線程衝突。unix的曆史負擔很多,比如線程局部儲存(TLS),個人認為就是為瞭解決早期程式設計者喜歡大量使用全域變數和多進程提出的。
那如果確實需要在多線程程式裡運行另一個程式(而且只有可執行檔沒有原始碼)怎麼辦呢。可以把要執行的程式封裝一下,多線程的主程式通過處理序間通訊的方式封裝後的子進程通訊。
與關於第2點多線程與signal機制的問題,網上很多,大家搜尋一下就有了,不多說了