C/C++并發(fā)編程(1)—— 并發(fā)/并行、多線程內(nèi)存模型

最近看了《七周七并發(fā)模型》[1]趟脂,對(duì)自己熟悉的C/C++并發(fā)編程有了很多新的思考泰讽。在Google上搜索“C C++ 并發(fā) 編程”,結(jié)果主要是Anthony的《C++ Concurrency in Action》以及零散的一些博文昔期。Anthony的書(shū)主要是教授C++最基礎(chǔ)的線程與鎖模型和無(wú)鎖編程的知識(shí)已卸,但是其它的并發(fā)模型書(shū)中并未提及。線程與鎖模型因其資料豐富“簡(jiǎn)單易學(xué)”被廣大C/C++程序員所使用硼一。該模型導(dǎo)致的死鎖累澡、饑餓等等問(wèn)題也是大家很頭痛的事情。實(shí)際上對(duì)于C/C++并發(fā)模型般贼,我們還有很多其它的選擇愧哟,比如Actor、CSP哼蛆、協(xié)程等蕊梧,而這正是這個(gè)C/++并發(fā)編程系列要告訴大家的。開(kāi)篇先說(shuō)一下并發(fā)編程的基礎(chǔ)知識(shí)腮介,并發(fā)與并行的區(qū)別和C/C++多線程內(nèi)存模型肥矢。
并發(fā)與并行的區(qū)別?
網(wǎng)絡(luò)上有很多關(guān)于“并發(fā)”與“并行”的解釋叠洗,大家比較認(rèn)同的是Golang大神Rob Pike在“并發(fā)不是并行[3]”的技術(shù)分享上的解釋:

Concurrency vs. parallelism
Concurrency is about dealing with lots of things at once.
Parallelism is about doing lots of things at once.
Not the same, but related.
Concurrency is about structure, parallelism is about execution.并發(fā)關(guān)乎結(jié)構(gòu)甘改,并行關(guān)乎執(zhí)行。
Concurrency provides a way to structure a solution to solve a problem that may (but not necessarily) be parallelizable.并發(fā)提供了一種方式讓我們能夠設(shè)計(jì)一種方案將問(wèn)題(非必須的)并行的解決灭抑。[2]

按我個(gè)人對(duì)以上的理解十艾,“并行”和“并發(fā)”的區(qū)別,可以簡(jiǎn)單理解為“并行 = 并發(fā)執(zhí)行”腾节。不管是多線程程序忘嫉、多進(jìn)程程序荤牍,在設(shè)計(jì)和實(shí)現(xiàn)階段應(yīng)該稱之為“并發(fā)”,而運(yùn)行時(shí)應(yīng)該稱之為“并行”榄融〔我可以類比我們熟悉的“程序 vs. 進(jìn)程”,運(yùn)行時(shí)的程序稱之為進(jìn)程愧杯。它們都是對(duì)同一個(gè)事物處在不同階段/狀態(tài)時(shí)的定義涎才。

C/C++多線程內(nèi)存模型

以前我認(rèn)為內(nèi)存模型和內(nèi)存布局是一回事,比如Linux下ELF可執(zhí)行文件格式力九,堆耍铜、棧、.data段跌前、.text段等等棕兼。實(shí)際上ELF這樣的內(nèi)存布局格式是Linux操作系統(tǒng)對(duì)可執(zhí)行程序的規(guī)范,不管用什么編程語(yǔ)言生成了直接(依賴運(yùn)行時(shí)“虛擬機(jī)”的語(yǔ)言除外)可運(yùn)行的程序抵乓,最終都是ELF的內(nèi)存布局伴挚。而內(nèi)存模型是編程語(yǔ)言和計(jì)算機(jī)系統(tǒng)(包括編譯器,多核CPU等可能對(duì)程序進(jìn)行亂序優(yōu)化的軟硬件)之間的契約灾炭,它規(guī)定了多個(gè)線程訪問(wèn)同一個(gè)內(nèi)存位置時(shí)的語(yǔ)義茎芋,以及某個(gè)線程對(duì)內(nèi)存位置的更新何時(shí)能被其它線程看見(jiàn)[4]
在C11/C++11標(biāo)準(zhǔn)之前蜈出,C/C++語(yǔ)言沒(méi)有內(nèi)存模型的定義田弥。在此期間,我們天真的認(rèn)為程序是按順序一致性(Sequential consistency)模型去運(yùn)行的铡原,而實(shí)際上編譯器和多核CPU卻是不滿足順序一致性模型的偷厦。Leslie在其論文[6]中定義了順序一致性模型需要滿足的兩個(gè)條件:

Rl: Each processor issues memory requests in the order specified by its program.

R2: Memory requests from all processors issued to an individual memory module are serviced from a single FIFO queue. Issuing a memory request consists of entering the request on this queue.

條件“R1”可以理解為“單個(gè)線程內(nèi)指令的執(zhí)行順序和代碼的順序是一致的”,而條件“R2”則讓多線程的指令執(zhí)行順序從全局來(lái)看是“串行”執(zhí)行的⊙嗫蹋現(xiàn)代CPU的緩存只泼、流水線和亂序執(zhí)行機(jī)制以及編譯器的代碼優(yōu)化、重排都無(wú)法滿足順序一致性模型卵洗。所以请唱,機(jī)器實(shí)際執(zhí)行的代碼并不是你寫(xiě)的代碼[9]

為了在性能和易編程性之間找到平衡忌怎,C++11提出了“sequential consistency for data race free programs”內(nèi)存模型,即沒(méi)有數(shù)據(jù)競(jìng)跑(data race)的程序符合順序一致性酪夷。數(shù)據(jù)競(jìng)跑是指多個(gè)線程在沒(méi)有同步的情況下去訪問(wèn)相同的內(nèi)存位置[5]榴啸。所以,在C11/C++11后晚岭,我們只要對(duì)多線程之間需要同步的變量和操作鸥印,使用正確的同步原語(yǔ)進(jìn)行同步,就能保證程序的執(zhí)行符合順序一致性。編譯器库说、多核CPU能保證其優(yōu)化措施不會(huì)破壞順序一致性狂鞋。
理論有些晦澀,我引用個(gè)例子說(shuō)明潜的。如下:

x = y = 0;
Thread1    Thread2
x = 1;     y = 1;
r1 = y;    r2 = x;

按照順序一致性模型骚揍,會(huì)有以下5種可能的執(zhí)行順序

從分析來(lái)看是不會(huì)出現(xiàn)“r1 = 0,r2 = 0”的情況的啰挪。但是C11/C++11之前并未規(guī)定多線程內(nèi)存模型信不,也沒(méi)有多線程的標(biāo)準(zhǔn)庫(kù)。pthread多線程庫(kù)是按照“單線程執(zhí)行模型(Single thread execution model)”來(lái)實(shí)現(xiàn)的亡呵。從編譯器的角度來(lái)看抽活,不存在什么多線程這樣的東西,程序就是一個(gè)代碼序列锰什。只要編譯優(yōu)化措施不影響順序執(zhí)行的結(jié)果下硕,就可以執(zhí)行這項(xiàng)優(yōu)化。比如下面這種優(yōu)化:

6
Thread1    Thread2
r1 = y;
           y = 1;
           r2 = x;
x = 1;

r1 = 0汁胆,r2 = 0

Thread1內(nèi)的“r1 = y”被換到了“x = 1”之前梭姓,這在C11/C++11標(biāo)準(zhǔn)之前是可能發(fā)生的。因?yàn)榘磫尉€程執(zhí)行模型沦泌,“給x賦值1”與“讀取y賦值給r1”是兩個(gè)不相關(guān)的事情糊昙,調(diào)換執(zhí)行順序不影響最終結(jié)果。而對(duì)于C11/C++11標(biāo)準(zhǔn)來(lái)說(shuō)谢谦,因?yàn)檫@段代碼不存在數(shù)據(jù)競(jìng)跑释牺,只要使用標(biāo)準(zhǔn)庫(kù)提供的線程操作來(lái)實(shí)現(xiàn),其執(zhí)行就符合順序一致性回挽,不會(huì)優(yōu)化出現(xiàn)“6”這種情況没咙。

另外,C11/C++11標(biāo)準(zhǔn)還明確了“內(nèi)存位置”的定義千劈。

一個(gè)內(nèi)存位置要么是標(biāo)量祭刚,要么是一組緊鄰的具有非零長(zhǎng)度的位域。
兩個(gè)線程可以互不干擾地對(duì)不同的內(nèi)存位置進(jìn)行讀寫(xiě)操作

比如有如下的結(jié)構(gòu)體:

struct
{
int a : 17;
int b : 15;
} x;

兩個(gè)線程分別讀寫(xiě)a和b墙牌,是否會(huì)互相干擾呢涡驮?畢竟CPU是按32/64位來(lái)取操作數(shù)的,而不是按17/15位來(lái)的喜滨。C11/C++11之前這樣的操作是未定義的捉捅,按C11/C++標(biāo)準(zhǔn)規(guī)定a和b則屬于同一個(gè)內(nèi)存位置。兩個(gè)線程分別對(duì)a虽风、b進(jìn)行讀寫(xiě)操作是會(huì)相互干擾的棒口,需要進(jìn)行同步寄月。或者將a无牵、b分割成兩個(gè)內(nèi)存位置:

struct
{
int a : 17;    // 內(nèi)存位置1
int : 0;
int b : 15;    // 內(nèi)存位置2
} x;

這樣編譯器會(huì)自動(dòng)自行內(nèi)存對(duì)齊漾肮,保證兩個(gè)線程分別讀寫(xiě)a、b互不干擾茎毁。

參考
[1]《七周七并發(fā)模型》克懊,Paul Butcher 著,黃炎 譯
[2] 也談并發(fā)與并行充岛,Tony Bai
[3] Concurrency is not parallelism保檐,Rob Pike
[4] 淺析C++多線程內(nèi)存模型,Guancheng (G.C.)
[5] Race Condition vs. Data Race崔梗,John Regehr
[6] How to Make a Multiprocessor Computer That Correctly Executes Multiprocess Programs夜只,1979,Leslie Lamport
[7] 《C++0x漫談》系列之:多線程內(nèi)存模型蒜魄,劉未鵬
[8] ISO/IEC JTC1 SC22 WG21 N3690扔亥,Programming Languages — C++
[9] C++ Memory Model,Valentin .etc

修訂記錄
2017-11-01 AM:從網(wǎng)易博客遷移到簡(jiǎn)書(shū)
2017-09-30 PM:完成初稿

版權(quán)聲明:自由轉(zhuǎn)載-非商用-非衍生-保持署名(創(chuàng)意共享3.0許可證

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末谈为,一起剝皮案震驚了整個(gè)濱河市旅挤,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌伞鲫,老刑警劉巖粘茄,帶你破解...
    沈念sama閱讀 218,386評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異秕脓,居然都是意外死亡柒瓣,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門(mén)吠架,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)芙贫,“玉大人,你說(shuō)我怎么就攤上這事傍药』瞧剑” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,704評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵拐辽,是天一觀的道長(zhǎng)拣挪。 經(jīng)常有香客問(wèn)我,道長(zhǎng)俱诸,這世上最難降的妖魔是什么菠劝? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,702評(píng)論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮乙埃,結(jié)果婚禮上闸英,老公的妹妹穿的比我還像新娘。我一直安慰自己介袜,他們只是感情好甫何,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,716評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著遇伞,像睡著了一般辙喂。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上鸠珠,一...
    開(kāi)封第一講書(shū)人閱讀 51,573評(píng)論 1 305
  • 那天巍耗,我揣著相機(jī)與錄音,去河邊找鬼渐排。 笑死炬太,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的驯耻。 我是一名探鬼主播亲族,決...
    沈念sama閱讀 40,314評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼可缚!你這毒婦竟也來(lái)了霎迫?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,230評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤帘靡,失蹤者是張志新(化名)和其女友劉穎知给,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體描姚,經(jīng)...
    沈念sama閱讀 45,680評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡涩赢,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,873評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了轰胁。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片谒主。...
    茶點(diǎn)故事閱讀 39,991評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖赃阀,靈堂內(nèi)的尸體忽然破棺而出霎肯,到底是詐尸還是另有隱情,我是刑警寧澤榛斯,帶...
    沈念sama閱讀 35,706評(píng)論 5 346
  • 正文 年R本政府宣布观游,位于F島的核電站,受9級(jí)特大地震影響驮俗,放射性物質(zhì)發(fā)生泄漏懂缕。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,329評(píng)論 3 330
  • 文/蒙蒙 一王凑、第九天 我趴在偏房一處隱蔽的房頂上張望搪柑。 院中可真熱鬧聋丝,春花似錦、人聲如沸工碾。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,910評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)渊额。三九已至况木,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間旬迹,已是汗流浹背火惊。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,038評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留奔垦,地道東北人屹耐。 一個(gè)月前我還...
    沈念sama閱讀 48,158評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像椿猎,于是被迫代替她去往敵國(guó)和親张症。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,941評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容