www.久久久久|狼友网站av天堂|精品国产无码a片|一级av色欲av|91在线播放视频|亚洲无码主播在线|国产精品草久在线|明星AV网站在线|污污内射久久一区|婷婷综合视频网站

當(dāng)前位置:首頁 > 公眾號(hào)精選 > C語言與CPP編程
[導(dǎo)讀]C++11其實(shí)主要就四方面內(nèi)容,第一個(gè)是可變參數(shù)模板,第二個(gè)是右值引用,第三個(gè)是智能指針,第四個(gè)是內(nèi)存模型(Memory Model)。相對(duì)來說,這也是較難理解的幾個(gè)特性,分別針對(duì)于泛型編程,內(nèi)存優(yōu)化,內(nèi)存管理和并發(fā)編程。

C++11其實(shí)主要就四方面內(nèi)容,第一個(gè)是可變參數(shù)模板,第二個(gè)是右值引用,第三個(gè)是智能指針,第四個(gè)是內(nèi)存模型(Memory Model)。

相對(duì)來說,這也是較難理解的幾個(gè)特性,分別針對(duì)于泛型編程,內(nèi)存優(yōu)化,內(nèi)存管理和并發(fā)編程。

并發(fā)編程是個(gè)非常大的模塊,而在諸多內(nèi)容底下有一個(gè)基本的概念,就是并發(fā)內(nèi)存模型(Memory Model)。

那么,什么是內(nèi)存模型?

1
Memory Model

早在之前介紹并發(fā)編程的文章中,我們就知道同步共享數(shù)據(jù)很重要。而同步可分為兩種方式:原子操作和順序約束。

原子操作是數(shù)據(jù)操作的最小單元,天生不可再分;順序約束可以協(xié)調(diào)各個(gè)線程之間數(shù)據(jù)訪問的先后順序,避免數(shù)據(jù)競(jìng)爭(zhēng)。

通常的同步方式會(huì)有兩個(gè)問題,一是效率不夠,二是死鎖問題。導(dǎo)致效率不夠是因?yàn)檫@些方式都是lock-based的。

當(dāng)然,若非非常在意效率,完全可以使用這些同步方式,因其簡(jiǎn)單方便且不易出錯(cuò)。

若要追求更高的效率,需要學(xué)習(xí)lock-free(無鎖)的同步方式。

內(nèi)存模型,簡(jiǎn)單地說,是一種介于開發(fā)者和系統(tǒng)之間的并發(fā)約定,可以無鎖地保證程序的執(zhí)行邏輯與預(yù)期一致。

這里的系統(tǒng)包括編譯器、處理器和緩存,各部分都想在自己的領(lǐng)域?qū)Τ绦蜻M(jìn)行優(yōu)化,以提高性能,而這些優(yōu)化會(huì)打亂源碼中的執(zhí)行順序。尤其是在多線程上,這些優(yōu)化會(huì)對(duì)共享數(shù)據(jù)造成巨大影響,導(dǎo)致程序的執(zhí)行結(jié)果往往不遂人意。

內(nèi)存模型,就是來解決這些優(yōu)化所帶來的問題。主要包含三個(gè)方面:

  • Atomic operations(原子操作)

  • Partial?ordering of operations(局部執(zhí)行順序)

  • Visible effects of operations(操作可見性)

原子操作和局部執(zhí)行順序如前所述,「操作可見性」指的是不同線程之間操作共享變量是可見的。

原子數(shù)據(jù)的同步是由編譯器來保證的,而非原子數(shù)據(jù)需要我們自己來規(guī)劃順序。

2

關(guān)系定義

這里有三種關(guān)系術(shù)語,

  • sequenced-before

  • happens-before

  • synchronizes-with

同一線程語句之間,若A操作在B操作之前執(zhí)行,則表示為A sequenced-before B,A的執(zhí)行結(jié)果對(duì)B可見。
而在不同線程的語句之間,若A操作在B操作之前就已發(fā)生,則表示為A happens-before B。該關(guān)系具有可傳遞性,也就是說,若A happens-before B,B happens-before C,則一定能得出A happens-before C。
若A操作的狀態(tài)改變引發(fā)了B操作的執(zhí)行,則表示為A synchronizes-with B。比如我們學(xué)過的事件、條件變量、信號(hào)量等等都會(huì)因一個(gè)條件(狀態(tài))滿足,而執(zhí)行相應(yīng)的操作,這種狀態(tài)關(guān)系就叫做synchronizes-with。
由于synchronizes-with的特性,可以借其實(shí)現(xiàn)happens-before關(guān)系。
內(nèi)存模型就是提供一個(gè)操作的約束語義,借其可以滿足上述關(guān)系,實(shí)現(xiàn)了順序約束。

3

Atomics(原子操作)

原子操作的知識(shí)之前也介紹過,限于篇幅,便不再捉細(xì)節(jié)。
先來整體看一下原子操作支持的操作類型,后面再來講應(yīng)用。
這里挑兩個(gè)來介紹一下相關(guān)操作,算是回顧。
第一個(gè)來講atomic_flag,這是最簡(jiǎn)單的原子類型,代表一個(gè)布爾標(biāo)志,可用它實(shí)現(xiàn)一個(gè)自旋鎖:

1#include?
2#include?
3#include?
4
5class?spin_lock
6{

7????std::atomic_flag?flag?=?ATOMIC_FLAG_INIT;
8public:
9????void?lock()?{?while(flag.test_and_set());?}
10
11????void?unlock()?{?flag.clear();?}
12};
13
14spin_lock?spin;
15int?g_num?=?0;
16void?work()
17
{
18????spin.lock();
19
20????g_num++;
21
22????spin.unlock();
23}
24
25int?main()
26
{
27????std::thread?t1(work);
28????std::thread?t2(work);
29????t1.join();
30????t2.join();
31
32????std::cout?<33
34????return?0;
35}

atomic_flag必須使用ATOMIC_FLAG_INIT初始化,該值就是0,也就是false。
只能通過兩個(gè)接口來操作atomic_flag:
  • clear:清除操作,將值設(shè)為false。

  • test_and_set:將值設(shè)為true并返回之前的值。


第9行的lock()函數(shù)實(shí)現(xiàn)了自旋鎖,當(dāng)?shù)谝粋€(gè)線程進(jìn)來的時(shí)候,由于atomic_flag為false,所以會(huì)通過test_and_set設(shè)置為true并返回false,第一個(gè)線程于是可以接著執(zhí)行下面的邏輯。

當(dāng)?shù)诙€(gè)線程進(jìn)來時(shí),flag為true,因此會(huì)一直循環(huán),只有第一個(gè)線程中unlock了才會(huì)接著執(zhí)行。由此保證了共享變量g_num。
第二個(gè)來講atomic,它所支持的原子操作要比atomic_flag多。
一個(gè)簡(jiǎn)單的同步操作:

1#include?
2#include?
3#include?
4#include?
5#include?
6#include?
7
8std::atomic<bool>?flag{false};
9std::vector<int>?shared_values;
10void?work()
11
{
12????std::cout?<"waiting"?<std::endl;
13????while(!flag.load())
14????{
15????????std::this_thread::sleep_for(std::chrono::milliseconds(5));
16????}
17
18????shared_values[1]?=?2;
19????std::cout?<"end?of?the?work"?<std::endl;
20}
21
22void?set_value()
23
{
24????shared_values?=?{?7,?8,?9?};
25????flag?=?true;
26????std::cout?<"data?prepared"?<std::endl;
27}
28
29int?main()
30
{
31????std::thread?t1(work);
32????std::thread?t2(set_value);
33????t1.join();
34????t2.join();
35
36????std::copy(shared_values.begin(),?shared_values.end(),?std::ostream_iterator<int>(std::cout,?"?"));
37
38????return?0;
39}

這里有兩個(gè)線程,它們之間擁有執(zhí)行順序,只有先在set_value函數(shù)中設(shè)置好共享值,才能在work函數(shù)中修改。
通過flag的load函數(shù)可以獲取原子值,在值未設(shè)置完成時(shí)其為false,所以會(huì)一直等待數(shù)據(jù)到來。當(dāng)flag變?yōu)閠rue時(shí),表示數(shù)據(jù)已經(jīng)設(shè)置完成,于是會(huì)繼續(xù)工作。

4

Memory ordering(內(nèi)存順序)

是什么保證了上述原子操作能夠在多線程環(huán)境下同步執(zhí)行呢?

其實(shí)在所有的原子操作函數(shù)中都有一個(gè)可選參數(shù)memory_order。比如atomic的load()和store()原型如下:

bool?std::_Atomic_bool::load(std::memory_order?_Order?=?std::memory_order_seq_cst)?const?noexcept
void?std::_Atomic_bool::store(bool?_Value,?std::memory_order?_Order?=?std::memory_order_seq_cst)?noexcept

這里的可選參數(shù)默認(rèn)為memory_order_seq_cst,所有的memory_order可選值為:

enum?memory_order?{

????memory_order_relaxed,
????memory_order_consume,
????memory_order_acquire,
????memory_order_release,
????memory_order_acq_rel,
????memory_order_seq_cst
};

這就是C++提供的如何實(shí)現(xiàn)順序約束的方式,通過指定特定的memory_order,可以實(shí)現(xiàn)前面提及的sequence-before、happens-before、synchronizes-with關(guān)系。

順序約束是我們和系統(tǒng)之間的一個(gè)約定,約定強(qiáng)度由強(qiáng)到弱可以分為三個(gè)層次:

  • Sequential consistency(順序一致性): memory_order_seq_cst
  • Acquire-release(獲取與釋放): memory_order_consume,memory_order_acquire,memory_order_release,memory_order_acq_rel
  • Relaxed(松散模型): memory_order_relaxed

Sequential consistency保證所有操作在線程之間都有一個(gè)全局的順序,Acquire-release保證在不同線程間對(duì)于相同的原子變量的寫和讀的操作順序,Relaxed僅保證原子的修改順序。

為何要分層次呢?

其實(shí)順序約束和系統(tǒng)優(yōu)化之間是一種零和博弈,約束越強(qiáng),系統(tǒng)所能夠做的優(yōu)化便越少。

因此每個(gè)層次擁有效率差異,層次越低,優(yōu)化越多,效率也越高,不過掌握難度也越大。

所有的Memory order按照操作類型,又可分為三類:

  • Read(讀):memory_order_acquire,memory_order_consume

  • Write(寫):memory_order_release

  • Read-modify-Write(讀-改-寫):memory_order_acq_rel,memory_order_seq_cst

Relaxed未定義同步和順序約束,所以要單獨(dú)而論。

例如load()就是Read操作,store()就是Write()操作,compare_exchange_strong就是Read-modify-Write操作。

這意味著你不能將一個(gè)Read操作的順序約束,寫到store()上。例如,若將memory_order_acquire寫到store()上,不會(huì)產(chǎn)生任何效果。

我們先來從默認(rèn)的Sequential consistency開始,往往無需設(shè)置,便默認(rèn)是memory_order_seq_cst,可以寫一個(gè)簡(jiǎn)單的生產(chǎn)者-消費(fèi)者函數(shù):

1std::string?sc_value;
2std::atomic<bool>?ready{false};
3
4void?consumer()
5
{
6????while(!ready.load())?{}
7
8????std::cout?<std::endl;
9}
10
11void?producer()
12
{
13????sc_value?=?"produce?values";
14????ready?=?true;
15}
16
17int?main()
18
{
19????std::thread?t1(consumer);
20????std::thread?t2(producer);
21????t1.join();
22????t2.join();
23
24????return?0;
25}

此時(shí),執(zhí)行順序具有強(qiáng)保證性,一定是先執(zhí)行了producer再執(zhí)行的consumer。
用標(biāo)準(zhǔn)的關(guān)系術(shù)語來說就是,第13行的操作和第14行的操作是sequenced-before關(guān)系,第14行和第6行的操作是synchronizes-with關(guān)系,進(jìn)而保證了14行的賦值操作一定在第6行的load()操作之前執(zhí)行,也就是保證了happens-before關(guān)系。
Acquire-release就開始變得有些復(fù)雜,我們先以一個(gè)最簡(jiǎn)單的例子來看。

1class?spin_lock
2{

3????std::atomic_flag?flag?=?ATOMIC_FLAG_INIT;
4public:
5????spin_lock()?{}
6
7????void?lock()?{?while(flag.test_and_set(std::memory_order_acquire));?}
8
9????void?unlock()?{?flag.clear(std::memory_order_release);?}
10};
11
12spin_lock?spin;
13void?work()
14
{
15????spin.lock();
16????//?do?something
17????spin.unlock();
18}
19
20int?main()
21
{
22????std::thread?t1(work);
23????std::thread?t2(work);
24????t1.join();
25????t2.join();
26
27????return?0;
28}

clear()中使用了release,test_and_set()中使用了acquire,acquire和release操作之間是synchronizes-with的關(guān)系。

它的行為和之前使用sequential consistency默認(rèn)參數(shù)的自旋鎖一樣,不過要更加輕便高效。

test_and_set()操作其實(shí)是個(gè)Read-modify-Write操作,不過依舊可以使用acquire操作。release禁止了所有在它之前或之后的寫操作亂序,acquire禁止了所有在它之前或之后的讀操作亂序。

在兩個(gè)不同的線程之間,共同訪問同一個(gè)原子是flag,所添加的順序約束就是為了保證flag的修改順序。

我們?cè)賮砜匆粋€(gè)更清晰的例子:

1std::atomic<bool>?x{false},?y{false};
2std::atomic<int>?z{0};
3void?write()
4
{
5????//?relaxed只保證修改順序
6????x.store(true,?std::memory_order_relaxed);
7
8????//?release保證在它之前的所有寫操作順序一致
9????y.store(true,?std::memory_order_release);
10}
11
12void?read()
13
{
14????//?acquire保證在它之前和之后的讀操作順序一致
15????while(!y.load(std::memory_order_acquire));
16
17????//?relaxed只保證修改順序
18????if(x.load(std::memory_order_relaxed))
19????????++z;
20}
21
22int?main()
23
{
24????std::thread?t1(write);
25????std::thread?t2(read);
26????t1.join();
27????t2.join();
28
29????assert(z.load()?!=?0);
30
31????return?0;
32}

注意這是使用了relaxed、release和acquire三種約束。
relaxed只保證修改順序,所以對(duì)于write()函數(shù)來說,一定是先執(zhí)行x后執(zhí)行y操作。不過若是將y也使用relaxed,雖然在write()中是先x后y的順序,而在read()的眼中,可能是先y后x的順序,這是優(yōu)化導(dǎo)致的。
而因?yàn)閥的讀和寫使用了acquire和release約束,所以可以保證在不同線程間對(duì)于相同的原子變量讀和寫的操作順序一致。
同時(shí),Acquire-release操作還擁有傳遞性,是典型的happens-before關(guān)系。
還是提供一個(gè)例子:

1std::vector<int>?shared_value;
2std::atomic<bool>?produced{false};
3std::atomic<bool>?consumed{false};
4
5void?producer()
6
{
7????shared_value?=?{?7,?8,?9?};
8
9????//?A.?realse happens-before B
10????produced.store(true,?std::memory_order_release);
11}
12
13void?delivery()
14
{
15????//?B.?acquire,A?synchronizes?with?B
16????while(!produced.load(std::memory_order_acquire));
17
18????//?B.?release happens-beforeC
19????consumed.store(true,?std::memory_order_release);
20}
21
22void?consumer()
23
{
24????//?C.?acquire,?B?synchronizes?with?C
25????//?therefore,?A?happens?before?C
26????while(!consumed.load(std::memory_order_acquire));
27
28????shared_value[1]?=?2;
29}
30
31int?main()
32
{
33????std::thread?t1(consumer);
34????std::thread?t2(producer);
35????std::thread?t3(delivery);
36????t1.join();
37????t2.join();
38????t3.join();
39
40????std::copy(shared_value.begin(),?shared_value.end(),?std::ostream_iterator<int>(std::cout,?"?"));
41
42????return?0;
43}

注釋已經(jīng)足夠說明其中所以,便不細(xì)述。

5

Fences(柵欄)

看回先前的一個(gè)例子:

1std::atomic<bool>?x{false},?y{false};
2std::atomic<int>?z{0};
3void?write()
4
{
5????//?relaxed只保證修改順序
6????x.store(true,?std::memory_order_relaxed);
7????y.store(true,?std::memory_order_relaxed);
8}
9
10void?read()
11
{
12????//?relaxed只保證修改順序
13????while(!y.load(std::memory_order_relaxed));
14????if(x.load(std::memory_order_relaxed))
15????????++z;
16}


relaxed是最弱的內(nèi)存模型,此處全使用relaxed,順序?qū)⒉辉儆斜WC。

也許在read()中看到的write()操作是先y后x,那么此時(shí)read()里面的if操作便無法滿足,也就是說,++z不會(huì)被執(zhí)行。

解決方法是結(jié)合fences來使用,只需添加兩行代碼:

1std::atomic<bool>?x{false},?y{false};
2std::atomic<int>?z{0};
3void?write()
4
{
5????//?relaxed只保證修改順序
6????x.store(true,?std::memory_order_relaxed);
7
8????std::atomic_thread_fence(std::memory_order_release);
9
10????y.store(true,?std::memory_order_relaxed);
11}
12
13void?read()
14
{
15????//?relaxed只保證修改順序
16????while(!y.load(std::memory_order_relaxed));
17
18????std::atomic_thread_fence(std::memory_order_acquire);
19
20????if(x.load(std::memory_order_relaxed))
21????????++z;
22}

fences位于relaxed操作之間,它像一個(gè)柵欄一樣,可以保證前后的操作不會(huì)亂序。
具體細(xì)節(jié),接著來看。
C++提供了兩個(gè)類型的fences,
  • std::atomic_thread_fence:同步線程之間的內(nèi)存訪問。

  • std::atomic_signal_fence:同步同一線程上的signal handler和code running。

我們主要學(xué)習(xí)第一個(gè)線程的fence,它會(huì)阻止特定的操作穿過柵欄,約束執(zhí)行順序。

有三種類型的fences,

  • Full fence:阻止兩個(gè)任意操作亂序。memory_order_seq_cst或memory_order_acq_rel。

  • Acquire fence:阻止讀操作亂序,memory_order_acquire。

  • Release fence:阻止寫操作亂序,memory_order_release。

用圖來表示為:

圖中間灰色的一杠就表示fence,紅色表示禁止亂序,可以看到,除了Store-Load,其它操作都可以保障執(zhí)行順序。
同樣也有效率差異,可以針對(duì)具體的操作來選擇合適的fence。

6

總結(jié)

本篇內(nèi)容挺復(fù)雜的,其實(shí)就包含三個(gè)方面:Atomic operations(原子操作)、Partial?ordering of operations(局部執(zhí)行順序)和Visible effects of operations(操作可見性)。

面對(duì)一個(gè)復(fù)雜的概念,往往需要變換尺度來進(jìn)行理解,若一開始便陷入諸多細(xì)節(jié)中去,難免迷失其中,看不到整體的結(jié)構(gòu)。
所以這里其實(shí)也就是以我自己的理解來寫,細(xì)節(jié)涉及不多,但整體結(jié)構(gòu)已算完整,想了解更多具體細(xì)節(jié)可以參考C++ Concurrency in Action

免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺(tái)僅提供信息存儲(chǔ)服務(wù)。文章僅代表作者個(gè)人觀點(diǎn),不代表本平臺(tái)立場(chǎng),如有問題,請(qǐng)聯(lián)系我們,謝謝!

本站聲明: 本文章由作者或相關(guān)機(jī)構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點(diǎn),本站亦不保證或承諾內(nèi)容真實(shí)性等。需要轉(zhuǎn)載請(qǐng)聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請(qǐng)及時(shí)聯(lián)系本站刪除。
換一批
延伸閱讀

9月2日消息,不造車的華為或?qū)⒋呱龈蟮莫?dú)角獸公司,隨著阿維塔和賽力斯的入局,華為引望愈發(fā)顯得引人矚目。

關(guān)鍵字: 阿維塔 塞力斯 華為

倫敦2024年8月29日 /美通社/ -- 英國汽車技術(shù)公司SODA.Auto推出其旗艦產(chǎn)品SODA V,這是全球首款涵蓋汽車工程師從創(chuàng)意到認(rèn)證的所有需求的工具,可用于創(chuàng)建軟件定義汽車。 SODA V工具的開發(fā)耗時(shí)1.5...

關(guān)鍵字: 汽車 人工智能 智能驅(qū)動(dòng) BSP

北京2024年8月28日 /美通社/ -- 越來越多用戶希望企業(yè)業(yè)務(wù)能7×24不間斷運(yùn)行,同時(shí)企業(yè)卻面臨越來越多業(yè)務(wù)中斷的風(fēng)險(xiǎn),如企業(yè)系統(tǒng)復(fù)雜性的增加,頻繁的功能更新和發(fā)布等。如何確保業(yè)務(wù)連續(xù)性,提升韌性,成...

關(guān)鍵字: 亞馬遜 解密 控制平面 BSP

8月30日消息,據(jù)媒體報(bào)道,騰訊和網(wǎng)易近期正在縮減他們對(duì)日本游戲市場(chǎng)的投資。

關(guān)鍵字: 騰訊 編碼器 CPU

8月28日消息,今天上午,2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會(huì)開幕式在貴陽舉行,華為董事、質(zhì)量流程IT總裁陶景文發(fā)表了演講。

關(guān)鍵字: 華為 12nm EDA 半導(dǎo)體

8月28日消息,在2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會(huì)上,華為常務(wù)董事、華為云CEO張平安發(fā)表演講稱,數(shù)字世界的話語權(quán)最終是由生態(tài)的繁榮決定的。

關(guān)鍵字: 華為 12nm 手機(jī) 衛(wèi)星通信

要點(diǎn): 有效應(yīng)對(duì)環(huán)境變化,經(jīng)營(yíng)業(yè)績(jī)穩(wěn)中有升 落實(shí)提質(zhì)增效舉措,毛利潤(rùn)率延續(xù)升勢(shì) 戰(zhàn)略布局成效顯著,戰(zhàn)新業(yè)務(wù)引領(lǐng)增長(zhǎng) 以科技創(chuàng)新為引領(lǐng),提升企業(yè)核心競(jìng)爭(zhēng)力 堅(jiān)持高質(zhì)量發(fā)展策略,塑強(qiáng)核心競(jìng)爭(zhēng)優(yōu)勢(shì)...

關(guān)鍵字: 通信 BSP 電信運(yùn)營(yíng)商 數(shù)字經(jīng)濟(jì)

北京2024年8月27日 /美通社/ -- 8月21日,由中央廣播電視總臺(tái)與中國電影電視技術(shù)學(xué)會(huì)聯(lián)合牽頭組建的NVI技術(shù)創(chuàng)新聯(lián)盟在BIRTV2024超高清全產(chǎn)業(yè)鏈發(fā)展研討會(huì)上宣布正式成立。 活動(dòng)現(xiàn)場(chǎng) NVI技術(shù)創(chuàng)新聯(lián)...

關(guān)鍵字: VI 傳輸協(xié)議 音頻 BSP

北京2024年8月27日 /美通社/ -- 在8月23日舉辦的2024年長(zhǎng)三角生態(tài)綠色一體化發(fā)展示范區(qū)聯(lián)合招商會(huì)上,軟通動(dòng)力信息技術(shù)(集團(tuán))股份有限公司(以下簡(jiǎn)稱"軟通動(dòng)力")與長(zhǎng)三角投資(上海)有限...

關(guān)鍵字: BSP 信息技術(shù)
關(guān)閉
關(guān)閉