2010-08-18 50 views
6

這是我的後續記憶管理問題上一篇文章。以下是我所知道的問題。線程相關的問題和調試

1)數據爭(原子性侵犯和數據損壞)

2)訂貨問題

3)鎖定的濫用導致死鎖

4)heisenbugs

任何其他事項多線程?如何解決它們?

回答

2

埃裏克的四個問題清單是非常重要的。但是,調試這些問題非常困難。

對於死鎖,我一直很喜歡「水平鎖」。基本上你給每種類型的鎖定一個級別號碼。然後要求一個線程鎖定是單調的。

要做到平整的鎖,你可以聲明的結構是這樣的:

typedef struct { 
    os_mutex actual_lock; 
    int level; 
    my_lock *prev_lock_in_thread; 
} my_lock_struct; 

static __tls my_lock_struct *last_lock_in_thread; 

void my_lock_aquire(int level, *my_lock_struct lock) { 
    if (last_lock_in_thread != NULL) assert(last_lock_in_thread->level < level) 
    os_lock_acquire(lock->actual_lock) 
    lock->level = level 
    lock->prev_lock_in_thread = last_lock_in_thread 
    last_lock_in_thread = lock 
} 

什麼是酷的平整鎖是死鎖的可能性會導致斷言。並與FUNCLINE一些額外的魔法,你確切地知道你的線程做了什麼壞事。

對於數據競爭和缺乏同步,目前情況相當糟糕。有一些靜態工具可以嘗試識別問題。但是誤報率很高。

我工作的公司(http://www.corensic.com)有一個名爲Jinx的新產品,主動尋找可以暴露競爭條件的案例。這是通過使用虛擬化技術來控制各個CPU上的線程交錯以及放大CPU之間的通信來完成的。

檢查出來。您可能還有幾天的時間免費下載Beta版本。

Jinx特別擅長查找無鎖數據結構中的錯誤。它也非常適合尋找其他競賽條件。最酷的是沒有誤報。如果您的代碼測試接近競爭條件,Jinx將幫助代碼走上糟糕的道路。但是,如果不存在不良道路,就不會有虛假的警告。

1

四個與theading最常見的問題是

1-死鎖
2-活鎖
3-競態條件
4-飢餓

+1

非常感謝,但是如何解決這些問題? – brett 2010-08-18 18:44:03

+0

所有這些問題都可以用信號量(鎖)來解決。你需要仔細瞭解你在做什麼。儘量不要用線程來完成它們,它們是幫助你的程序做東西的工具,而不是你需要把所有東西放在一起的一些魔術技巧 – Eric 2010-08-18 18:54:26

1

如何解決[與多線程的問題] ?

「調試」MT應用程序的一個好方法是通過日誌記錄。擁有豐富過濾選項的良好日誌記錄庫使得它更容易。當然,日誌記錄本身會影響時間,所以你仍然可能會有「heisenbugs」,但它比真正進入調試器的可能性要小得多。

準備和計劃。從一開始就在您的應用程序中包含一個好的日誌記錄工具

2

不幸的是,沒有好的藥丸可以幫助自動解決大部分/所有線程問題。即使單元測試在單線程代碼上運行良好,也可能永遠不會檢測到非常微妙的競爭條件。

有一件事會幫助的是保持線程交互數據封裝在對象中。對象的界面/範圍越小,在檢查中檢測錯誤就越容易(可能還有測試,但競爭條件在測試案例中可能是一種痛苦)。通過保留一個可以使用的簡單接口,使用該接口的客戶端也將在默認情況下正確。通過從許多較小的部分構建一個更大的系統(其中只有少數實際進行線程交互),您可以在避免線程錯誤方面邁出很長的一步。

1

使您的線程儘可能簡單。

儘量不要使用全局變量。全局常量(從不改變的實際常量)很好。當你需要使用全局或共享變量時,你需要用某種類型的互斥/鎖(信號量,監視器等)來保護它們。

確保您真正瞭解您的互斥鎖如何工作。有幾種不同的實現可以以不同的方式工作。

嘗試組織您的代碼,以便關鍵部分(您持有某種類型的鎖的地方)儘可能快。請注意,某些功能可能會阻塞(睡眠或等待某些內容,並阻止操作系統允許該線程繼續運行一段時間)。不要在持有任何鎖的時候使用這些鎖(除非絕對必要或在調試過程中,因爲它有時會顯示其他錯誤)。

試着瞭解更多的線程爲你做了什麼。盲目地拋出更多的線索來解決問題往往會讓事情變得更糟。不同的線程競爭CPU和鎖。

死鎖避免需要計劃。儘量避免一次只能獲取一個以上的鎖。如果這是不可避免的決定你將用來獲取和釋放所有線程的鎖的順序。確保你知道什麼是死鎖真的意味着什麼。

調試多線程或分佈式應用程序很困難。如果您可以在單線程環境中執行大部分調試(甚至可能只是強制其他線程進入休眠狀態),那麼您可以在跳入多線程調試之前嘗試消除非線程中心錯誤。

總是想想其他線程可能要做些什麼。在您的代碼中評論這一點。如果你以某種方式做某事,因爲你知道當時沒有其他線程應該訪問某個資源,請寫一個大的評論,這樣說。

你可能想換到互斥鎖調用/解鎖像其它功能:

INT my_lock_get(鎖類型的鎖,爲const char *文件,無符號線,爲const char * MSG){

thread_id_type me = this_thread(); 

logf("%u\t%s (%u)\t%s:%u\t%s\t%s\n", time_now(), thread_name(me), me, "get", msg); 

lock_get(lock); 

logf("%u\t%s (%u)\t%s:%u\t%s\t%s\n", time_now(), thread_name(me), me, "in", msg); 

}

和解鎖的類似版本。請注意,這裏使用的函數和類型都是由任何一個API編寫的,而不是過度的。

使用類似這樣的東西,如果出現錯誤並且使用perl腳本或類似的東西來運行日誌查詢來檢查事情出錯的地方(例如匹配鎖和解鎖),可以返回。

請注意,打印或日誌記錄功能也可能需要鎖定它。許多圖書館已經擁有這種內置的功能,但並非都是如此。這些鎖不需要使用lock_ [get | release]函數的打印版本,否則您將擁有無限遞歸。

1
  1. 謹防全局變量,即使 他們const,特別是在 C++。只有POD靜態地 初始化「àla」C在這裏很好。 只要運行時構造函數 開始起作用,請注意極其 。具有靜態鏈接變量的AFAIR初始化順序 處於 不同的編譯單位是 以未定義的順序調用。也許 C++類,其初始化所有 其成員正確,並有一個 空的函數體,現在可以確定 ,但我曾經也有一個不好的 經驗。

    這其中的原因之一,在 POSIX側pthread_mutex_t很多 容易比sem_t編程:它 有一個靜態初始化 PTHREAD_MUTEX_INITIALIZER

  2. 保持臨界段越短 可能有兩個原因:它可能 在年底更有效,但 更重要的是它更容易 維護和調試。

    一個關鍵部分不應該 長,一個屏幕,其中包括 鎖定,同時還需要 保護它解鎖,並且包括 的意見和主張,幫助 讀者瞭解什麼是 發生。

    開始實施關鍵部分 非常嚴格的可能是一個全局鎖定,然後放鬆 約束。

  3. 如果很多 線程開始在相同的 時間開始寫入,則記錄可能很困難。如果每個線程都做了 合理的工作量嘗試 讓他們各自寫一個自己的文件 ,這樣他們就不會互相連接 。

    但請注意,日誌更改行爲 的代碼。當錯誤 消失時,這可能很糟糕,或者當錯誤 出現,否則您不會注意到 時有益。

    爲了 這些亂七八糟的事後分析,你必須對每行 這樣,所有的文件可以 合併,給你執行一個一致的看法 準確時間戳。

1

- >向該列表中添加優先級倒置。

由於另一張海報隱瞞,日誌文件是美好的事情。對於死鎖,使用LogLock而不是Lock可以幫助您確定實體何時停止工作。也就是說,一旦你知道你有一個死鎖,日誌會告訴你何時何地鎖實例化和釋放。這可以幫助我們跟蹤這些事情。

我發現使用Actor模型時遵循相同的消息 - > confirm-> confirm收到的樣式的競爭條件似乎消失。這就是說,YMMV。