鍍金池/ 教程/ C/ 10.1 與并發(fā)相關(guān)的錯誤類型
3.4 本章總結(jié)
6.3 基于鎖設計更加復雜的數(shù)據(jù)結(jié)構(gòu)
6.1 為并發(fā)設計的意義何在?
5.2 <code>C++</code>中的原子操作和原子類型
A.7 自動推導變量類型
2.1 線程管理的基礎
8.5 在實踐中設計并發(fā)代碼
2.4 運行時決定線程數(shù)量
2.2 向線程函數(shù)傳遞參數(shù)
第4章 同步并發(fā)操作
2.3 轉(zhuǎn)移線程所有權(quán)
8.3 為多線程性能設計數(shù)據(jù)結(jié)構(gòu)
6.4 本章總結(jié)
7.3 對于設計無鎖數(shù)據(jù)結(jié)構(gòu)的指導建議
關(guān)于這本書
A.1 右值引用
2.6 本章總結(jié)
D.2 &lt;condition_variable&gt;頭文件
A.6 變參模板
6.2 基于鎖的并發(fā)數(shù)據(jù)結(jié)構(gòu)
4.5 本章總結(jié)
A.9 本章總結(jié)
前言
第10章 多線程程序的測試和調(diào)試
5.4 本章總結(jié)
第9章 高級線程管理
5.1 內(nèi)存模型基礎
2.5 識別線程
第1章 你好,C++的并發(fā)世界!
1.2 為什么使用并發(fā)?
A.5 Lambda函數(shù)
第2章 線程管理
4.3 限定等待時間
D.3 &lt;atomic&gt;頭文件
10.2 定位并發(fā)錯誤的技術(shù)
附錄B 并發(fā)庫的簡單比較
5.3 同步操作和強制排序
A.8 線程本地變量
第8章 并發(fā)代碼設計
3.3 保護共享數(shù)據(jù)的替代設施
附錄D C++線程庫參考
第7章 無鎖并發(fā)數(shù)據(jù)結(jié)構(gòu)設計
D.7 &lt;thread&gt;頭文件
D.1 &lt;chrono&gt;頭文件
4.1 等待一個事件或其他條件
A.3 默認函數(shù)
附錄A 對<code>C++</code>11語言特性的簡要介紹
第6章 基于鎖的并發(fā)數(shù)據(jù)結(jié)構(gòu)設計
封面圖片介紹
7.2 無鎖數(shù)據(jù)結(jié)構(gòu)的例子
8.6 本章總結(jié)
8.1 線程間劃分工作的技術(shù)
4.2 使用期望等待一次性事件
8.4 設計并發(fā)代碼的注意事項
D.5 &lt;mutex&gt;頭文件
3.1 共享數(shù)據(jù)帶來的問題
資源
9.3 本章總結(jié)
10.3 本章總結(jié)
10.1 與并發(fā)相關(guān)的錯誤類型
D.4 &lt;future&gt;頭文件
3.2 使用互斥量保護共享數(shù)據(jù)
9.1 線程池
1.1 何謂并發(fā)
9.2 中斷線程
4.4 使用同步操作簡化代碼
A.2 刪除函數(shù)
1.3 C++中的并發(fā)和多線程
1.4 開始入門
第5章 C++內(nèi)存模型和原子類型操作
消息傳遞框架與完整的ATM示例
8.2 影響并發(fā)代碼性能的因素
7.1 定義和意義
D.6 &lt;ratio&gt;頭文件
A.4 常量表達式函數(shù)
7.4 本章總結(jié)
1.5 本章總結(jié)
第3章 線程間共享數(shù)據(jù)

10.1 與并發(fā)相關(guān)的錯誤類型

你可以在并發(fā)代碼中發(fā)現(xiàn)各式各樣的錯誤,這些錯誤不會集中于某個方面。不過,有一些錯誤與使用并發(fā)直接相關(guān),本章重點關(guān)注這些錯誤。通常,并發(fā)相關(guān)的錯誤通常有兩大類:

  • 不必要阻塞

  • 條件競爭

這兩大類的顆粒度很大,讓我們將其分成顆粒度較小的問題。

10.1.1 不必要阻塞

“不必要阻塞”是什么意思?一個線程被阻塞的時候,不能處理任何任務,因為它在等待其他“條件”的達成。通常這些“條件”就是一個互斥量、一個條件變量或一個future,也可能是一個I/O操作。這是多線程代碼的先天特性,不過這也不是在任何時候都可取的——衍生成“不必要阻塞”。你會問:為什么不需要阻塞?通常,是因為其他線程在等待該阻塞線程上的某些操作完成,如果該線程阻塞了,那那些線程必然會被阻塞。

這個主題可以分成以下幾個問題:

  • 死鎖——如你在第3章所見,在死鎖的情況下,兩個線程會互相等待。當線程產(chǎn)生死鎖,應該完成的任務就會持續(xù)擱置。舉個例子來說,一些線程是負責對用戶界面操作的線程,在死鎖的情況下,用戶界面就會無響應。在另一些例子中,界面接口會保持響應,不過有些任務就無法完成,比如:查詢無結(jié)果返回,或文檔未打印。

  • 活鎖——與死鎖的情況類似。不同的地方在于線程不是阻塞等待,而是在循環(huán)中持續(xù)檢查,例如:自旋鎖。一些比較嚴重的情況下,其表現(xiàn)和死鎖一樣(應用不會做任何處理,停止響應),CPU的使用率還居高不下;因為線程還在循環(huán)中被檢查,而不是阻塞等待。在一些不太嚴重的情況下,因為使用隨機調(diào)度,活鎖的問題還是可以解決的。

  • I/O阻塞或外部輸入——當線程被外部輸入所阻塞,線程也就不能做其他事情了(即使,等待輸入的情況永遠不會發(fā)生)。因此,被外部輸入所阻塞,就會讓人不太高興,因為可能有其他線程正在等待這個線程完成某些任務。

簡單的介紹了一下“不必要阻塞”的組成。

那么,條件競爭呢?

10.1.2 條件競爭

條件競爭在多線程代碼中很常見——很多條件競爭表現(xiàn)為死鎖與活鎖。而且,并非所有條件競爭都是惡性的——對獨立線程相關(guān)操作的調(diào)度,決定了條件競爭發(fā)生的時間。很多條件競爭是良性的,比如:哪一個線程去處理任務隊列中的下一個任務。不過,很多并發(fā)錯誤的引起也是因為條件競爭。

特別是,條件競爭經(jīng)常會產(chǎn)生以下幾種類型的錯誤:

  • 數(shù)據(jù)競爭——因為未同步訪問一塊共享內(nèi)存,將會導致代碼產(chǎn)生未定義行為。在第5章已經(jīng)介紹了數(shù)據(jù)競爭,也了解了C++的內(nèi)存模型。數(shù)據(jù)競爭通常發(fā)生在錯誤的使用原子操作,做同步線程的時候,或沒使用互斥量所保護的共享數(shù)據(jù)的時候。

  • 破壞不變量——主要表現(xiàn)為懸空指針(因為其他線程已經(jīng)將要訪問的數(shù)據(jù)刪除了),隨機存儲錯誤(因為局部更新,導致線程讀取了不一樣的數(shù)據(jù)),以及雙重釋放(比如:當兩個線程對同一個隊列同時執(zhí)行pop操作,想要刪除同一個關(guān)聯(lián)數(shù)據(jù)),等等。不變量被破壞可以看作為“基于數(shù)據(jù)”的問題。當獨立線程需要以一定順序執(zhí)行某些操作時,錯誤的同步會導致條件競爭,比如:順序被破壞。

  • 生命周期問題——雖然這類問題也能歸結(jié)為破壞了不變量,不過這里將其作為一個單獨的類別給出。這里的問題是,線程會訪問不存在的數(shù)據(jù),這可能是因為數(shù)據(jù)被刪除或銷毀了,或者轉(zhuǎn)移到其他對象中去了。生命周期問題,通常是在一個線程引用了局部變量,在線程還沒有完成前,局部變量的“死期”就已經(jīng)到了,不過這個問題并不止存在這種情況下。當你手動調(diào)用join()等待線程完成工作,你需要保證異常拋出的時候,join()還會等待其他未完成工作的線程。這是線程中基本異常安全的應用。

惡性條件競爭就如同一個殺手。死鎖和活鎖會表現(xiàn)為:應用掛起和反應遲鈍,或超長時間完成任務。當一個線程產(chǎn)生死鎖或活鎖,可以用調(diào)試器附著到該線程上進行調(diào)試。條件競爭,破壞不變量,以及生命周期問題,其表現(xiàn)都是代碼可見的(比如,隨機崩潰或錯誤輸出)——可能重寫了系統(tǒng)部分的內(nèi)存使用方式(不會改太多)。其中,可能是因為執(zhí)行時間,導致問題無法定位到具體的位置。這是共享內(nèi)存系統(tǒng)的詛咒——需要通過線程嘗試限制可訪問的數(shù)據(jù),并且還要正確的使用同步,應用中的任何線程都可以復寫(可被其他線程訪問的)數(shù)據(jù)。

現(xiàn)在已經(jīng)了解了這兩大類中都有哪些具體問題了。

下面就讓我們來了解,如何在你的代碼中定位和修復這些問題。