2009-01-31 242 views
54

這是一個跟進到this question,在這一點上我沒有得到任何輸入。這裏是一個簡單的問題:如何檢測和調試多線程問題?

是否有可能檢測和調試來自多線程代碼的問題?

通常我們必須告訴我們的客戶:「我們不能在這裏重現問題,所以我們無法修復它,請告訴我們重現問題的步驟,然後我們會解決它。」如果我知道這是一個多線程問題,但是大多數情況下我不知道這是一個不知何故的答案。我怎麼才能知道一個問題是一個多線程問題,以及如何進行調試?

我想知道是否有任何特殊的日誌框架,調試技術或代碼檢查器,或其他任何東西來幫助解決這些問題。一般方法是受歡迎的。如果任何答案應該與語言相關,那麼將它保持爲.NET和Java。

回答

68

線程/併發問題是衆所周知難以複製的 - 這就是爲什麼您應該設計避免或至少將概率降至最低的原因之一。這就是不變對象如此寶貴的原因。嘗試將可變對象分離到單個線程,然後仔細控制線程之間的可變對象交換。嘗試使用對象移交的設計進行編程,而不是「共享」對象。對於後者,使用完全同步的控制對象(這些對象更容易推理),並避免讓同步對象利用其他必須同步的對象 - 也就是儘量保持它們獨立。你最好的防守是一個很好的設計。

死鎖是最容易調試的,如果在死鎖時可以獲得堆棧跟蹤。鑑於痕跡,其中大部分進行死鎖檢測,很容易找出原因,然後推理代碼爲什麼以及如何修復它。隨着死鎖,它總是會以不同的順序獲取相同的鎖。

活鎖更難 - 在錯誤狀態下能夠觀察系統是您最好的選擇。

種族條件往往是非常難以複製,更難以從手動代碼審查識別。有了這些,我通常會採用的路徑,除了大量的複製測試之外,還是要推斷可能性,並嘗試記錄信息來證明或反駁理論。如果你有直接的國家腐敗證據,你可以根據腐敗來推斷可能的原因。

系統越複雜,找到併發錯誤就越困難,並推斷它的行爲。利用JVisualVM和遠程連接分析器等工具 - 如果您可以連接到處於錯誤狀態的系統並檢查線程和對象,它們可以成爲救命稻草。

此外,請注意可能的行爲差異,這些差異取決於CPU內核數量,流水線數量,總線帶寬等。硬件變化可能會影響您複製問題的能力。一些問題只會出現在單核CPU上,其他只在多核上纔會出現。

最後一件事,嘗試使用與系統庫一起發佈的併發對象 - 例如Java java.util.concurrent是你的朋友。編寫自己的併發控制對象很困難,充滿危險;如果您有選擇,請留給專家。

+4

有些工具可以檢測競爭條件。對於.NET,您可以查看Microsoft的[CHESS](http://research.microsoft.com/en-us/projects/chess/),它會嘗試通過針對每種可能的交錯運行代碼來檢測競爭狀況。對於Java,您可以獲得[ThreadSafe](http://www.contemplateltd.com/threadsafe),該工具專門用於檢測和調試併發錯誤。 – 2014-03-18 11:42:31

+0

FindBugs也是一個很好的靜態分析工具,用於查找潛在的線程錯誤。 – 2014-03-18 16:25:11

7

我認爲你得到你的other questionanswer是相當不錯的。但我會強調這些觀點。

僅修改共享狀態中的一個關鍵部分(互斥)

獲取鎖在一組順序和以相反的順序釋放。

使用預先構建的抽象儘可能(像java.util.concurrent中的東西)

此外,一些分析工具可以檢測到一些潛在的問題。例如,FindBugs可以在Java程序中發現一些線程問題。這些工具無法找到所有問題(它們不是銀彈),但它們可以提供幫助。

由於vanslly在對此答案的評論中指出,學習良好的日誌記錄輸出也很有幫助,但要小心Heisenbugs

+0

有關捕獲錯誤狀態或異常是什麼。像異常記錄,幷包括堆棧跟蹤? – Llyle 2009-01-31 21:58:00

4

假設我有難以複製的麻煩的報告,我總是通過閱讀代碼來找到這些代碼,最好是對代碼閱讀,因此您可以討論線程語義/鎖定需求。當我們根據報告的問題做到這一點時,我發現我們總是相當快地確定了一個或多個問題。我認爲解決難題也是一種相當便宜的技術。

對不起,不能告訴你按ctrl + shift + f13,但我不認爲有這樣的可用。但只想着究竟是什麼通常在代碼中給出了相當強烈的方向感,所以你不必從main()開始。

+0

是的,談論代碼應該做什麼以及它實際上在做什麼會有很大的幫助! – MicSim 2009-02-01 11:49:44

1

Visual Studio允許您檢查每個線程的調用堆棧,並且可以在它們之間切換。跟蹤各種線程問題是不夠的,但這是一個開始。計劃在即將到來的VS2010中進行多線程調試的改進。

我已經在.NET代碼中使用WinDbg + SoS進行線程化問題。您可以檢查鎖(同步blokcs),線程調用堆棧等。

+0

SoS是一個在WinDbg下啓用託管調試的DLL。 – 2009-02-01 12:41:43

4

除了您已經得到的其他好的答案之外:請始終在至少具有與客戶使用的處理器/處理器內核數量相同的機器上進行測試,或者因爲程序中存在活動線程。否則,一些多線程錯誤可能難以複製。

5

除了崩潰轉儲,一種技術是廣泛的運行時日誌記錄:每個線程記錄它在做什麼。

然後,報告錯誤時的第一個問題可能是:「日誌文件在哪裏?」

有時你可以在日誌文件中看到問題:「這個線程在這裏檢測到一個非法/意外的狀態......然後看看,這個線程是這樣做的,就在這之前和/或之後。「

如果日誌文件沒有說明發生了什麼,然後向客戶道歉,爲代碼添加足夠多的額外日誌語句,將新代碼提供給客戶,並說您將在修復後它發生一次。

+0

用日誌文件分析多線程問題通常會改變程序的動態行爲。一旦出現問題,最好使用內存跟蹤並將複製內存跟蹤複製到文件中。在我的答案中查看更多詳情。 – 2017-12-09 21:55:31

-6

我能想到的最好的辦法是遠離多線程代碼儘可能遠。似乎有很少有程序員可以編寫無bug的多線程應用程序,我會爭辯說沒有編碼人員可以編寫無bug的代碼large multi線程應用程序

+0

那麼,你怎麼知道任何大型應用程序(單線程或多線程)都沒有bug?你不能。但除此之外,如果您知道並遵守規則,我認爲可以編寫相對安全的多線程代碼。但是,大多數時間試圖獲得更好的性能會引入錯誤。 – mghie 2009-01-31 22:21:43

1

assert()是檢測競爭條件的朋友。每當你進入一個關鍵部分時,斷言與它相關的不變量是真的(這就是CS的用途)。雖然不幸的是,支票可能很昂貴,因此不適合在生產環境中使用。

5

對於Java,有一個名爲javapathfinder的驗證工具,我發現它有助於調試和驗證多線程應用程序對代碼中潛在的競爭條件和死鎖錯誤。
它與Eclipse和Netbean IDE都能正常工作。

0

我遇到了一個線程問題,它給出了相同的錯誤結果,並且因爲每次其他條件(內存,調度程序,處理負載)差不多都是相同的。

根據我的經驗,我可以說HARDEST PART認識到它是一個線程問題,BEST解決方案是仔細查看多線程代碼。只需仔細查看線程代碼,就可以找出可能出錯的地方。其他方式(線程轉儲,分析器等)將排在第二位。

1

我實現了工具vmlens以在運行時檢測java程序中的競爭條件。它實現了一種稱爲eraser的算法。

3

有時,多線程解決方案無法避免。如果有錯誤,則需要實時進行調查,這對於Visual Studio等大多數工具來說幾乎是不可能的。唯一的解決辦法是寫的痕跡,但跟蹤本身應該:

  1. 沒有增加任何延遲
  2. 沒有使用任何鎖定
  3. 進行多線程安全
  4. 跟蹤以正確的順序發生了什麼事。

這聽起來像一個不可能完成的任務,但可以通過將跟蹤寫入內存來輕鬆實現。在C#中,它會是這個樣子:

public const int MaxMessages = 0x100; 
string[] messages = new string[MaxMessages]; 
int messagesIndex = -1; 

public void Trace(string message) { 
    int thisIndex = Interlocked.Increment(ref messagesIndex); 
    messages[thisIndex] = message; 
} 

的方法跟蹤()是多線程安全的,無阻塞,可以從任何線程調用。在我的電腦上,執行需要大約2微秒,這應該足夠快。

在您認爲出現問題的地方添加Trace()指令,讓程序運行,等到發生錯誤時停止跟蹤,然後調查跟蹤以查找任何錯誤。

用於這種方法也收集線程和定時信息,更詳細的描述回收緩衝區和輸出跟蹤很好,你可以找到在: CodeProject上:實時調試多線程代碼1

1

小圖帶在調試多線程代碼時需要考慮一些調試技術。 圖表不斷增長,請留下評論和提示添加。 (在this link更新文件)

Multithreaded debugging chart

-2

我使用GNU和使用簡單的腳本

$更gdb_tracer

b func.cpp:2871 
r 
#c 
while (1) 
next 
#step 
end