鍍金池/ 教程/ C/ 1.2 為什么使用并發(fā)?
3.4 本章總結(jié)
6.3 基于鎖設計更加復雜的數(shù)據(jù)結(jié)構
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)移線程所有權
8.3 為多線程性能設計數(shù)據(jù)結(jié)構
6.4 本章總結(jié)
7.3 對于設計無鎖數(shù)據(jù)結(jié)構的指導建議
關于這本書
A.1 右值引用
2.6 本章總結(jié)
D.2 &lt;condition_variable&gt;頭文件
A.6 變參模板
6.2 基于鎖的并發(fā)數(shù)據(jù)結(jié)構
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ā)錯誤的技術
附錄B 并發(fā)庫的簡單比較
5.3 同步操作和強制排序
A.8 線程本地變量
第8章 并發(fā)代碼設計
3.3 保護共享數(shù)據(jù)的替代設施
附錄D C++線程庫參考
第7章 無鎖并發(fā)數(shù)據(jù)結(jié)構設計
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é)構設計
封面圖片介紹
7.2 無鎖數(shù)據(jù)結(jié)構的例子
8.6 本章總結(jié)
8.1 線程間劃分工作的技術
4.2 使用期望等待一次性事件
8.4 設計并發(fā)代碼的注意事項
D.5 &lt;mutex&gt;頭文件
3.1 共享數(shù)據(jù)帶來的問題
資源
9.3 本章總結(jié)
10.3 本章總結(jié)
10.1 與并發(fā)相關的錯誤類型
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ù)

1.2 為什么使用并發(fā)?

主要原因有兩個:關注點分離(SOC)和性能。事實上,它們應該是使用并發(fā)的唯一原因;如果你觀察得足夠仔細,所有因素都可以歸結(jié)到其中的一個原因(或者可能是兩個都有。當然,除了像“就因為我愿意”這樣的原因之外)。

1.2.1 為了分離關注點

編寫軟件時,分離關注點是個好主意;通過將相關的代碼與無關的代碼分離,可以使程序更容易理解和測試,從而減少出錯的可能性。即使一些功能區(qū)域中的操作需要在同一時刻發(fā)生的情況下,依舊可以使用并發(fā)分離不同的功能區(qū)域;若不顯式地使用并發(fā),就得編寫一個任務切換框架,或者在操作中主動地調(diào)用一段不相關的代碼。

考慮一個有用戶界面的處理密集型應用——DVD播放程序。這樣的應用程序,應具備這兩種功能:一,要從光盤中讀出數(shù)據(jù),對圖像和聲音進行解碼,之后把解碼出的信號輸出至視頻和音頻硬件,從而實現(xiàn)DVD的無誤播放;二,還需要接受來自用戶的輸入,當用戶單擊“暫?!?、“返回菜單”或“退出”按鍵的時候執(zhí)行對應的操作。當應用是單個線程時,應用需要在回放期間定期檢查用戶的輸入,這就需要把“DVD播放”代碼和“用戶界面”代碼放在一起,以便調(diào)用。如果使用多線程方式來分隔這些關注點,“用戶界面”代碼和“DVD播放”代碼就不再需要放在一起:一個線程可以處理“用戶界面”事件,另一個進行“DVD播放”。它們之間會有交互(用戶點擊“暫停”),不過任務間需要人為的進行關聯(lián)。

這會給響應性帶來一些錯覺,因為用戶界面線程通??梢粤⒓错憫脩舻恼埱?,在當請求傳達給忙碌線程,這時的相應可以是簡單地顯示代表忙碌的光標或“請等待”字樣的消息。類似地,獨立的線程通常用來執(zhí)行那些必須在后臺持續(xù)運行的任務,例如,桌面搜索程序中監(jiān)視文件系統(tǒng)變化的任務。因為它們之間的交互清晰可辨,所以這種方式會使每個線程的邏輯變的更加簡單。

在這種情況下,線程的數(shù)量不再依賴CPU中的可用內(nèi)核的數(shù)量,因為對線程的劃分是基于概念上的設計,而不是一種增加吞吐量的嘗試。

1.2.2 為了性能

多處理器系統(tǒng)已經(jīng)存在了幾十年,但直到最近,它們也只在超級計算機、大型機和大型服務器系統(tǒng)中才能看到。然而,芯片制造商越來越傾向于多核芯片的設計,即在單個芯片上集成2、4、16或更多的處理器,從而獲取更好的性能。因此,多核臺式計算機、多核嵌入式設備,現(xiàn)在越來越普遍。它們計算能力的提高不是源自使單一任務運行的更快,而是并行運行多個任務。在過去,程序員曾坐看他們的程序隨著處理器的更新?lián)Q代而變得更快,無需他們這邊做任何事。但是現(xiàn)在,就像Herb Sutter所說的,“沒有免費的午餐了?!盵1] 如果想要利用日益增長的計算能力,那就必須設計多任務并發(fā)式軟件。程序員必須留意這個,尤其是那些迄今都忽略并發(fā)的人們,現(xiàn)在很有必要將其加入工具箱中了。

兩種方式利用并發(fā)提高性能:第一,將一個單個任務分成幾部分,且各自并行運行,從而降低總運行時間。這就是任務并行(task parallelism)。雖然這聽起來很直觀,但它是一個相當復雜的過程,因為在各個部分之間可能存在著依賴。區(qū)別可能是在過程方面——一個線程執(zhí)行算法的一部分,而另一個線程執(zhí)行算法的另一個部分——或是在數(shù)據(jù)方面——每個線程在不同的數(shù)據(jù)部分上執(zhí)行相同的操作(第二種方式)。后一種方法被稱為數(shù)據(jù)并行(data parallelism)。

第一種并行方式影響的算法常被稱為易并行(embarrassingly parallel)算法。盡管易并行算法的代碼會讓你感覺到頭痛,但這對于你來說是一件好事:我曾遇到過自然并行(naturally parallel)和便利并發(fā)(conveniently concurrent)的算法。易并行算法具有良好的可擴展特性——當可用硬件線程的數(shù)量增加時,算法的并行性也會隨之增加。這種算法能很好的體現(xiàn)人多力量大。如果算法中有不易并行的部分,你可以把算法劃分成固定(不可擴展)數(shù)量的并行任務。第8章將會再來討論,在線程之間劃分任務的技巧。

第二種方法是使用可并行的方式,來解決更大的問題;與其同時處理一個文件,不如酌情處理2個、10個或20個。雖然,這是數(shù)據(jù)并行的一種應用(通過對多組數(shù)據(jù)同時執(zhí)行相同的操作),但著重點不同。處理一個數(shù)據(jù)塊仍然需要同樣的時間,但在相同的時間內(nèi)處理了更多的數(shù)據(jù)。當然,這種方法也有限制,并非在所有情況下都是有益的。不過,這種方法所帶來的吞吐量提升,可以讓某些新功能成為可能,例如,可以并行處理圖片的各部分,就能提高視頻的分辨率。

1.2.3 什么時候不使用并發(fā)

知道何時不使用并發(fā)與知道何時使用它一樣重要?;旧?,不使用并發(fā)的唯一原因就是,收益比不上成本。使用并發(fā)的代碼在很多情況下難以理解,因此編寫和維護的多線程代碼就會產(chǎn)生直接的腦力成本,同時額外的復雜性也可能引起更多的錯誤。除非潛在的性能增益足夠大或關注點分離地足夠清晰,能抵消所需的額外的開發(fā)時間以及與維護多線程代碼相關的額外成本(代碼正確的前提下);否則,別用并發(fā)。

同樣地,性能增益可能會小于預期;因為操作系統(tǒng)需要分配內(nèi)核相關資源和堆??臻g,所以在啟動線程時存在固有的開銷,然后才能把新線程加入調(diào)度器中,所有這一切都需要時間。如果在線程上的任務完成得很快,那么任務實際執(zhí)行的時間要比啟動線程的時間小很多,這就會導致應用程序的整體性能還不如直接使用“產(chǎn)生線程”的方式。

此外,線程是有限的資源。如果讓太多的線程同時運行,則會消耗很多操作系統(tǒng)資源,從而使得操作系統(tǒng)整體上運行得更加緩慢。不僅如此,因為每個線程都需要一個獨立的堆??臻g,所以運行太多的線程也會耗盡進程的可用內(nèi)存或地址空間。對于一個可用地址空間為4GB(32bit)的平坦架構的進程來說,這的確是個問題:如果每個線程都有一個1MB的堆棧(很多系統(tǒng)都會這樣分配),那么4096個線程將會用盡所有地址空間,不會給代碼、靜態(tài)數(shù)據(jù)或者堆數(shù)據(jù)留有任何空間。即便64位(或者更大)的系統(tǒng)不存在這種直接的地址空間限制,但其他資源有限:如果你運行了太多的線程,最終也是出會問題的。盡管線程池(參見第9章)可以用來限制線程的數(shù)量,但這也并不是什么靈丹妙藥,它也有自己的問題。

當客戶端/服務器(C/S)應用在服務器端為每一個鏈接啟動一個獨立的線程,對于少量的鏈接是可以正常工作的,但當同樣的技術用于需要處理大量鏈接的高需求服務器時,也會因為線程太多而耗盡系統(tǒng)資源。在這種場景下,使用線程池可以對性能產(chǎn)生優(yōu)化(參見第9章)。

最后,運行越多的線程,操作系統(tǒng)就需要做越多的上下文切換,每一次切換都需要耗費本可以花在有價值工作上的時間。所以在某些時候,增加一個額外的線程實際上會降低,而非提高應用程序的整體性能。為此,如果你試圖得到系統(tǒng)的最佳性能,可以考慮使用硬件并發(fā)(或不用),并調(diào)整運行線程的數(shù)量。

為性能而使用并發(fā)就像所有其他優(yōu)化策略一樣:它擁有大幅度提高應用性能的潛力,但它也可能使代碼復雜化,使其更難理解,并更容易出錯。因此,只有應用中具有顯著增益潛力的性能關鍵部分,才值得并發(fā)化。當然,如果性能收益的潛力僅次于設計清晰或關注點分離,可能也值得使用多線程設計。

假設你已經(jīng)決定確實要在應用中使用并發(fā),無論是為了性能、關注點分離,亦或是因為多線程星期一(multithreading Monday)(譯者:可能是學習多線程的意思)。

問題又來了,對于C++程序員來說,多線程意味著什么?


[1] “The Free Lunch Is Over: A Fundamental Turn Toward Concurrency in Software,” Herb Sutter, Dr. Dobb’s Journal, 30(3), March 2005. http://www.gotw.ca/publications/concurrency-ddj.htm.