鍍金池/ 教程/ C/ 5.1 內(nèi)存模型基礎(chǔ)
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 對(duì)于設(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章 多線程程序的測(cè)試和調(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ā)庫的簡(jiǎn)單比較
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 對(duì)<code>C++</code>11語言特性的簡(jiǎn)要介紹
第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 使用同步操作簡(jiǎn)化代碼
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ù)

5.1 內(nèi)存模型基礎(chǔ)

這里從兩方面來講內(nèi)存模型:一方面是基本結(jié)構(gòu),這與事務(wù)在內(nèi)存中是怎樣布局的有關(guān);另一方面就是并發(fā)。對(duì)于并發(fā)基本結(jié)構(gòu)很重要,特別是在低層原子操作。所以我將會(huì)從基本結(jié)構(gòu)講起。C++中它與所有的對(duì)象和內(nèi)存位置有關(guān)。

5.1.1 對(duì)象和內(nèi)存位置

在一個(gè)C++程序中的所有數(shù)據(jù)都是由對(duì)象(objects)構(gòu)成。這不是說你可以創(chuàng)建一個(gè)int的衍生類,或者是基本類型中存在有成員函數(shù),或是像在Smalltalk和Ruby語言下討論程序那樣——“一切都是對(duì)象”?!皩?duì)象”僅僅是對(duì)C++數(shù)據(jù)構(gòu)建塊的一個(gè)聲明。C++標(biāo)準(zhǔn)定義類對(duì)象為“存儲(chǔ)區(qū)域”,但對(duì)象還是可以將自己的特性賦予其他對(duì)象,比如,其類型和生命周期。

像int或float這樣的對(duì)象就是簡(jiǎn)單基本類型;當(dāng)然,也有用戶定義類的實(shí)例。一些對(duì)象(比如,數(shù)組,衍生類的實(shí)例,特殊(具有非靜態(tài)數(shù)據(jù)成員)類的實(shí)例)擁有子對(duì)象,但是其他對(duì)象就沒有。

無論對(duì)象是怎么樣的一個(gè)類型,一個(gè)對(duì)象都會(huì)存儲(chǔ)在一個(gè)或多個(gè)內(nèi)存位置上。每一個(gè)內(nèi)存位置不是一個(gè)標(biāo)量類型的對(duì)象,就是一個(gè)標(biāo)量類型的子對(duì)象,比如,unsigned short、my_class*或序列中的相鄰位域。當(dāng)你使用位域,就需要注意:雖然相鄰位域中是不同的對(duì)象,但仍視其為相同的內(nèi)存位置。如圖5.1所示,將一個(gè)struct分解為多個(gè)對(duì)象,并且展示了每個(gè)對(duì)象的內(nèi)存位置。

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

圖5.1 分解一個(gè)struct,展示不同對(duì)象的內(nèi)存位置

首先,完整的struct是一個(gè)有多個(gè)子對(duì)象(每一個(gè)成員變量)組成的對(duì)象。位域bf1和bf2共享同一個(gè)內(nèi)存位置(int是4字節(jié)、32位類型),并且std::string類型的對(duì)象s由內(nèi)部多個(gè)內(nèi)存位置組成,但是其他的每個(gè)成員都擁有自己的內(nèi)存位置。注意,位域?qū)挾葹?的bf3是如何與bf4分離,并擁有各自的內(nèi)存位置的。(譯者注:圖中bf3是一個(gè)錯(cuò)誤展示,在C++和C中規(guī)定,寬度為0的一個(gè)未命名位域強(qiáng)制下一位域?qū)R到其下一type邊界,其中type是該成員的類型。這里使用命名變量為0的位域,可能只是想展示其與bf4是如何分離的。有關(guān)位域的更多可以參考wiki的頁面)。

這里有四個(gè)需要牢記的原則:

  1. 每一個(gè)變量都是一個(gè)對(duì)象,包括作為其成員變量的對(duì)象。
  2. 每個(gè)對(duì)象至少占有一個(gè)內(nèi)存位置。
  3. 基本類型都有確定的內(nèi)存位置(無論類型大小如何,即使他們是相鄰的,或是數(shù)組的一部分)。
  4. 相鄰位域是相同內(nèi)存中的一部分。

我確定你會(huì)好奇,這些在并發(fā)中有什么作用,那么下面就讓我們來見識(shí)一下。

5.1.2 對(duì)象、內(nèi)存位置和并發(fā)

這部分對(duì)于C++的多線程應(yīng)用來說是至關(guān)重要的:所有東西都在內(nèi)存中。當(dāng)兩個(gè)線程訪問不同的內(nèi)存位置時(shí),不會(huì)存在任何問題,一切都工作順利。而另一種情況下,當(dāng)兩個(gè)線程訪問同一個(gè)內(nèi)存位置,你就要小心了。如果沒有線程更新內(nèi)存位置上的數(shù)據(jù),那還好;只讀數(shù)據(jù)不需要保護(hù)或同步。當(dāng)有線程對(duì)內(nèi)存位置上的數(shù)據(jù)進(jìn)行修改,那就有可能會(huì)產(chǎn)生條件競(jìng)爭(zhēng),就如第3章所述的那樣。

為了避免條件競(jìng)爭(zhēng),兩個(gè)線程就需要一定的執(zhí)行順序。第一種方式,如第3章所述那樣,使用互斥量來確定訪問的順序;當(dāng)同一互斥量在兩個(gè)線程同時(shí)訪問前被鎖住,那么在同一時(shí)間內(nèi)就只有一個(gè)線程能夠訪問到對(duì)應(yīng)的內(nèi)存位置,所以后一個(gè)訪問必須在前一個(gè)訪問之后。另一種方式是使用原子操作同步機(jī)制(詳見5.2節(jié)中對(duì)于原子操作的定義),決定兩個(gè)線程的訪問順序。使用原子操作來規(guī)定順序在5.3節(jié)中會(huì)有介紹。當(dāng)多于兩個(gè)線程訪問同一個(gè)內(nèi)存地址時(shí),對(duì)每個(gè)訪問這都需要定義一個(gè)順序。

如果不去規(guī)定兩個(gè)不同線程對(duì)同一內(nèi)存地址訪問的順序,那么訪問就不是原子的;并且,當(dāng)兩個(gè)線程都是“作者”時(shí),就會(huì)產(chǎn)生數(shù)據(jù)競(jìng)爭(zhēng)和未定義行為。

以下的聲明由為重要:未定義的行為是C++中最黑暗的角落。根據(jù)語言的標(biāo)準(zhǔn),一旦應(yīng)用中有任何未定義的行為,就很難預(yù)料會(huì)發(fā)生什么事情;因?yàn)?,未定義行為是難以預(yù)料的。我就知道一個(gè)未定義行為的特定實(shí)例,讓某人的顯示器起火的案例。雖然,這種事情應(yīng)該不會(huì)發(fā)生在你身上,但是數(shù)據(jù)競(jìng)爭(zhēng)絕對(duì)是一個(gè)嚴(yán)重的錯(cuò)誤,并且需要不惜一切代價(jià)避免它。

另一個(gè)重點(diǎn)是:當(dāng)程序中的對(duì)同一內(nèi)存地址中的數(shù)據(jù)訪問存在競(jìng)爭(zhēng),你可以使用原子操作來避免未定義行為。當(dāng)然,這不會(huì)影響競(jìng)爭(zhēng)的產(chǎn)生——原子操作并沒有指定訪問順序——但原子操作把程序拉回了定義行為的區(qū)域內(nèi)。

在我們了解原子操作前,還有一個(gè)有關(guān)對(duì)象和內(nèi)存地址的概念需要重點(diǎn)了解:修改順序。

5.1.3 修改順序

每一個(gè)在C++程序中的對(duì)象,都有(由程序中的所有線程對(duì)象)確定好的修改順序,在的初始化開始階段確定。在大多數(shù)情況下,這個(gè)順序不同于執(zhí)行中的順序,但是在給定的執(zhí)行程序中,所有線程都需要遵守這順序。如果對(duì)象不是一個(gè)原子類型(將在5.2節(jié)詳述),你必要確保有足夠的同步操作,來確定每個(gè)線程都遵守了變量的修改順序。當(dāng)不同線程在不同序列中訪問同一個(gè)值時(shí),你可能就會(huì)遇到數(shù)據(jù)競(jìng)爭(zhēng)或未定義行為(詳見5.1.2節(jié))。如果你使用原子操作,編譯器就有責(zé)任去替你做必要的同步。

這一要求意味著:投機(jī)執(zhí)行是不允許的,因?yàn)楫?dāng)線程按修改順序訪問一個(gè)特殊的輸入,之后的讀操作,必須由線程返回較新的值,并且之后的寫操作必須發(fā)生在修改順序之后。同樣的,在同一線程上允許讀取對(duì)象的操作,要不返回一個(gè)已寫入的值,要不在對(duì)象的修改順序后(也就是在讀取后)再寫入另一個(gè)值。雖然,所有線程都需要遵守程序中每個(gè)獨(dú)立對(duì)象的修改順序,但它們沒有必要遵守在獨(dú)立對(duì)象上的相對(duì)操作順序。在5.3.3節(jié)中會(huì)有更多關(guān)于不同線程間操作順序的內(nèi)容。

所以,什么是原子操作?它如何來規(guī)定順序?接下來的一節(jié)中,會(huì)為你揭曉答案。