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

1.1 何謂并發(fā)

最簡單和最基本的并發(fā),是指兩個(gè)或更多獨(dú)立的活動(dòng)同時(shí)發(fā)生。

并發(fā)在生活中隨處可見,我們可以一邊走路一邊說話,也可以兩只手同時(shí)作不同的動(dòng)作,還有我們每個(gè)人都過著相互獨(dú)立的生活——當(dāng)我在游泳的時(shí)候,你可以看球賽,等等。

1.1.1 計(jì)算機(jī)系統(tǒng)中的并發(fā)

計(jì)算機(jī)領(lǐng)域的并發(fā)指的是在單個(gè)系統(tǒng)里同時(shí)執(zhí)行多個(gè)獨(dú)立的任務(wù),而非順序的進(jìn)行一些活動(dòng)。

計(jì)算機(jī)領(lǐng)域里,并發(fā)不是一個(gè)新事物:很多年前,一臺(tái)計(jì)算機(jī)就能通過多任務(wù)操作系統(tǒng)的切換功能,同時(shí)運(yùn)行多個(gè)應(yīng)用程序;高端多處理器服務(wù)器在很早就已經(jīng)實(shí)現(xiàn)了真正的并行計(jì)算。那“老東西”上有哪些“新東西”能讓它在計(jì)算機(jī)領(lǐng)域越來越流行呢?——真正任務(wù)并行,而非一種錯(cuò)覺。

以前,大多數(shù)計(jì)算機(jī)只有一個(gè)處理器,具有單個(gè)處理單元(processing unit)或核心(core),如今還有很多這樣的臺(tái)式機(jī)。這種機(jī)器只能在某一時(shí)刻執(zhí)行一個(gè)任務(wù),不過它可以每秒進(jìn)行多次任務(wù)切換。通過“這個(gè)任務(wù)做一會(huì),再切換到別的任務(wù),再做一會(huì)兒”的方式,讓任務(wù)看起來是并行執(zhí)行的。這種方式稱為任務(wù)切換。如今,我們?nèi)匀粚⑦@樣的系統(tǒng)稱為并發(fā):因?yàn)槿蝿?wù)切換得太快,以至于無法感覺到任務(wù)在何時(shí)會(huì)被暫時(shí)掛起,而切換到另一個(gè)任務(wù)。任務(wù)切換會(huì)給用戶和應(yīng)用程序造成一種“并發(fā)的假象”。因?yàn)檫@種假象,當(dāng)應(yīng)用在任務(wù)切換的環(huán)境下和真正并發(fā)環(huán)境下執(zhí)行相比,行為還是有著微妙的不同。特別是對內(nèi)存模型不正確的假設(shè)(詳見第5章),在多線程環(huán)境中可能不會(huì)出現(xiàn)(詳見第10章)。

多處理器計(jì)算機(jī)用于服務(wù)器和高性能計(jì)算已有多年?;趩涡径嗪颂幚砥?多核處理器)的臺(tái)式機(jī),也越來越大眾化。無論擁有幾個(gè)處理器,這些機(jī)器都能夠真正的并行多個(gè)任務(wù)。我們稱其為硬件并發(fā)(hardware concurrency)”。

圖1.1顯示了一個(gè)計(jì)算機(jī)處理恰好兩個(gè)任務(wù)時(shí)的理想情景,每個(gè)任務(wù)被分為10個(gè)相等大小的塊。在一個(gè)雙核機(jī)器(具有兩個(gè)處理核心)上,每個(gè)任務(wù)可以在各自的處理核心上執(zhí)行。在單核機(jī)器上做任務(wù)切換時(shí),每個(gè)任務(wù)的塊交織進(jìn)行。但它們中間有一小段分隔(圖中所示灰色分隔條的厚度大于雙核機(jī)器的分隔條);為了實(shí)現(xiàn)交織進(jìn)行,系統(tǒng)每次從一個(gè)任務(wù)切換到另一個(gè)時(shí)都需要切換一次上下文(context switch),任務(wù)切換也有時(shí)間開銷。進(jìn)行上下文的切換時(shí),操作系統(tǒng)必須為當(dāng)前運(yùn)行的任務(wù)保存CPU的狀態(tài)和指令指針,并計(jì)算出要切換到哪個(gè)任務(wù),并為即將切換到的任務(wù)重新加載處理器狀態(tài)。然后,CPU可能要將新任務(wù)的指令和數(shù)據(jù)的內(nèi)存載入到緩存中,這會(huì)阻止CPU執(zhí)行任何指令,從而造成的更多的延遲。

http://wiki.jikexueyuan.com/project/cplusplus-concurrency-action/images/chapter1/1-1.png" alt="" />

圖 1.1 并發(fā)的兩種方式:雙核機(jī)器的真正并行 Vs. 單核機(jī)器的任務(wù)切換

有些處理器可以在一個(gè)核心上執(zhí)行多個(gè)線程,但硬件并發(fā)在多處理器或多核系統(tǒng)上效果更加顯著。硬件線程最重要的因素是數(shù)量,也就是硬件上可以并發(fā)運(yùn)行多少獨(dú)立的任務(wù)。即便是具有真正硬件并發(fā)的系統(tǒng),也很容易擁有比硬件“可并行最大任務(wù)數(shù)”還要多的任務(wù)需要執(zhí)行,所以任務(wù)切換在這些情況下仍然適用。例如,在一個(gè)典型的臺(tái)式計(jì)算機(jī)上可能會(huì)有成百上千個(gè)的任務(wù)在運(yùn)行,即便是在計(jì)算機(jī)處于空閑時(shí),還是會(huì)有后臺(tái)任務(wù)在運(yùn)行。正是任務(wù)切換使得這些后臺(tái)任務(wù)可以運(yùn)行,并使得你可以同時(shí)運(yùn)行文字處理器、編譯器、編輯器和web瀏覽器(或其他應(yīng)用的組合)。圖1.2顯示了四個(gè)任務(wù)在雙核處理器上的任務(wù)切換,仍然是將任務(wù)整齊地劃分為同等大小塊的理想情況。實(shí)際上,許多因素會(huì)使得分割不均和調(diào)度不規(guī)則。部分因素將在第8章中討論,那時(shí)我們再來看一看影響并行代碼性能的因素。

無論應(yīng)用程序在單核處理器,還是多核處理器上運(yùn)行;也不論是任務(wù)切換還是真正的硬件并發(fā),這里提到的技術(shù)、功能和類(本書所涉及的)都能使用得到。如何使用并發(fā),將很大程度上取決于可用的硬件并發(fā)。我們將在第8章中再次討論這個(gè)問題,并具體研究C++代碼并行設(shè)計(jì)的問題。

http://wiki.jikexueyuan.com/project/cplusplus-concurrency-action/images/chapter1/1-2.png" alt="" />

圖 1.2 四個(gè)任務(wù)在兩個(gè)核心之間的切換

1.1.2 并發(fā)的途徑

試想當(dāng)兩個(gè)程序員在兩個(gè)獨(dú)立的辦公室一起做一個(gè)軟件項(xiàng)目,他們可以安靜地工作、不互相干擾,并且他們?nèi)耸忠惶讌⒖际謨?。但是,他們溝通起來就有些困難,比起可以直接互相交談,他們必須使用電話、電子郵件或到對方的辦公室進(jìn)行直接交流。并且,管理兩個(gè)辦公室需要有一定的經(jīng)費(fèi)支出,還需要購買多份參考手冊。

假設(shè),讓開發(fā)人員同在一間辦公室辦公,他們可以自由的對某個(gè)應(yīng)用程序設(shè)計(jì)進(jìn)行討論,也可以在紙或白板上輕易的繪制圖表,對設(shè)計(jì)觀點(diǎn)進(jìn)行輔助性闡釋。現(xiàn)在,你只需要管理一個(gè)辦公室,只要有一套參考資料就夠了。遺憾的是,開發(fā)人員可能難以集中注意力,并且還可能存在資源共享的問題(比如,“參考手冊哪去了?”)

以上兩種方法,描繪了并發(fā)的兩種基本途徑。每個(gè)開發(fā)人員代表一個(gè)線程,每個(gè)辦公室代表一個(gè)進(jìn)程。第一種途徑是每個(gè)進(jìn)程只要一個(gè)線程,這就類似讓每個(gè)開發(fā)人員擁有自己的辦公室,而第二種途徑是每個(gè)進(jìn)程有多個(gè)線程,如同一個(gè)辦公室里有兩個(gè)開發(fā)人員。讓我們在一個(gè)應(yīng)用程序中簡單的分析一下這兩種途徑。

多進(jìn)程并發(fā)

使用并發(fā)的第一種方法,是將應(yīng)用程序分為多個(gè)獨(dú)立的進(jìn)程,它們在同一時(shí)刻運(yùn)行,就像同時(shí)進(jìn)行網(wǎng)頁瀏覽和文字處理一樣。如圖1.3所示,獨(dú)立的進(jìn)程可以通過進(jìn)程間常規(guī)的通信渠道傳遞訊息(信號(hào)、套接字、文件、管道等等)。不過,這種進(jìn)程之間的通信通常不是設(shè)置復(fù)雜,就是速度慢,這是因?yàn)椴僮飨到y(tǒng)會(huì)在進(jìn)程間提供了一定的保護(hù)措施,以避免一個(gè)進(jìn)程去修改另一個(gè)進(jìn)程的數(shù)據(jù)。還有一個(gè)缺點(diǎn)是,運(yùn)行多個(gè)進(jìn)程所需的固定開銷:需要時(shí)間啟動(dòng)進(jìn)程,操作系統(tǒng)需要內(nèi)部資源來管理進(jìn)程,等等。

當(dāng)然,以上的機(jī)制也不是一無是處:操作系統(tǒng)在進(jìn)程間提供附加的保護(hù)操作和更高級(jí)別的通信機(jī)制,意味著可以更容易編寫安全的并發(fā)代碼。實(shí)際上,在類似于Erlang的編程環(huán)境中,將進(jìn)程作為并發(fā)的基本構(gòu)造塊。

使用多進(jìn)程實(shí)現(xiàn)并發(fā)還有一個(gè)額外的優(yōu)勢———可以使用遠(yuǎn)程連接(可能需要聯(lián)網(wǎng))的方式,在不同的機(jī)器上運(yùn)行獨(dú)立的進(jìn)程。雖然,這增加了通信成本,但在設(shè)計(jì)精良的系統(tǒng)上,這可能是一個(gè)提高并行可用行和性能的低成本方式。

http://wiki.jikexueyuan.com/project/cplusplus-concurrency-action/images/chapter1/1-3.png" alt="" />

圖 1.3 一對并發(fā)運(yùn)行的進(jìn)程之間的通信

多線程并發(fā)

并發(fā)的另一個(gè)途徑,在單個(gè)進(jìn)程中運(yùn)行多個(gè)線程。線程很像輕量級(jí)的進(jìn)程:每個(gè)線程相互獨(dú)立運(yùn)行,且線程可以在不同的指令序列中運(yùn)行。但是,進(jìn)程中的所有線程都共享地址空間,并且所有線程訪問到大部分?jǐn)?shù)據(jù)———全局變量仍然是全局的,指針、對象的引用或數(shù)據(jù)可以在線程之間傳遞。雖然,進(jìn)程之間通常共享內(nèi)存,但是這種共享通常是難以建立和管理的。因?yàn)?,同一?shù)據(jù)的內(nèi)存地址在不同的進(jìn)程中是不相同。圖1.4展示了一個(gè)進(jìn)程中的兩個(gè)線程通過共享內(nèi)存進(jìn)行通信。

http://wiki.jikexueyuan.com/project/cplusplus-concurrency-action/images/chapter1/1-4.png" alt="" />

圖 1.4 同一進(jìn)程中的一對并發(fā)運(yùn)行的線程之間的通信

地址空間共享,以及缺少線程間數(shù)據(jù)的保護(hù),使得操作系統(tǒng)的記錄工作量減小,所以使用多線程相關(guān)的開銷遠(yuǎn)遠(yuǎn)小于使用多個(gè)進(jìn)程。不過,共享內(nèi)存的靈活性是有代價(jià)的:如果數(shù)據(jù)要被多個(gè)線程訪問,那么程序員必須確保每個(gè)線程所訪問到的數(shù)據(jù)是一致的(在本書第3、4、5和8章中會(huì)涉及,線程間數(shù)據(jù)共享可能會(huì)遇到的問題,以及如何使用工具來避免這些問題)。問題并非無解,只要在編寫代碼時(shí)適當(dāng)?shù)刈⒁饧纯?,這同樣也意味著需要對線程通信做大量的工作。

多個(gè)單線程/進(jìn)程間的通信(包含啟動(dòng))要比單一進(jìn)程中的多線程間的通信(包括啟動(dòng))的開銷大,若不考慮共享內(nèi)存可能會(huì)帶來的問題,多線程將會(huì)成為主流語言(包括C++)更青睞的并發(fā)途徑。此外,C++標(biāo)準(zhǔn)并未對進(jìn)程間通信提供任何原生支持,所以使用多進(jìn)程的方式實(shí)現(xiàn),這會(huì)依賴與平臺(tái)相關(guān)的API。因此,本書只關(guān)注使用多線程的并發(fā),并且在此之后所提到“并發(fā)”,均假設(shè)為多線程來實(shí)現(xiàn)。

了解并發(fā)后,讓來看看為什么要使用并發(fā)。