C++11其實主要就四方面內(nèi)容,第一個是可變參數(shù)模板,第二個是右值引用,第三個是智能指針,第四個是內(nèi)存模型(Memory Model)。
相對來說,這也是較難理解的幾個特性,分別針對于泛型編程,內(nèi)存優(yōu)化,內(nèi)存管理和并發(fā)編程。
并發(fā)編程是個非常大的模塊,而在諸多內(nèi)容底下有一個基本的概念,就是并發(fā)內(nèi)存模型(Memory Model)。
那么,什么是內(nèi)存模型?
早在之前介紹并發(fā)編程的文章中,我們就知道同步共享數(shù)據(jù)很重要。而同步可分為兩種方式:原子操作和順序約束。
原子操作是數(shù)據(jù)操作的最小單元,天生不可再分;順序約束可以協(xié)調(diào)各個線程之間數(shù)據(jù)訪問的先后順序,避免數(shù)據(jù)競爭。
通常的同步方式會有兩個問題,一是效率不夠,二是死鎖問題。導致效率不夠是因為這些方式都是lock-based的。
當然,若非非常在意效率,完全可以使用這些同步方式,因其簡單方便且不易出錯。
若要追求更高的效率,需要學習lock-free(無鎖)的同步方式。
內(nèi)存模型,簡單地說,是一種介于開發(fā)者和系統(tǒng)之間的并發(fā)約定,可以無鎖地保證程序的執(zhí)行邏輯與預期一致。
這里的系統(tǒng)包括編譯器、處理器和緩存,各部分都想在自己的領域對程序進行優(yōu)化,以提高性能,而這些優(yōu)化會打亂源碼中的執(zhí)行順序。尤其是在多線程上,這些優(yōu)化會對共享數(shù)據(jù)造成巨大影響,導致程序的執(zhí)行結果往往不遂人意。
內(nèi)存模型,就是來解決這些優(yōu)化所帶來的問題。主要包含三個方面:
Atomic operations(原子操作)
Partial?ordering of operations(局部執(zhí)行順序)
Visible effects of operations(操作可見性)
原子操作和局部執(zhí)行順序如前所述,「操作可見性」指的是不同線程之間操作共享變量是可見的。
原子數(shù)據(jù)的同步是由編譯器來保證的,而非原子數(shù)據(jù)需要我們自己來規(guī)劃順序。
2
關系定義
這里有三種關系術語,
sequenced-before
happens-before
synchronizes-with
3
Atomics(原子操作)
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}
clear:清除操作,將值設為false。
test_and_set:將值設為true并返回之前的值。
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}
4
Memory ordering(內(nèi)存順序)
是什么保證了上述原子操作能夠在多線程環(huán)境下同步執(zhí)行呢?
其實在所有的原子操作函數(shù)中都有一個可選參數(shù)memory_order。比如atomic
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ù)默認為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++提供的如何實現(xiàn)順序約束的方式,通過指定特定的memory_order,可以實現(xiàn)前面提及的sequence-before、happens-before、synchronizes-with關系。
順序約束是我們和系統(tǒng)之間的一個約定,約定強度由強到弱可以分為三個層次:
-
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保證所有操作在線程之間都有一個全局的順序,Acquire-release保證在不同線程間對于相同的原子變量的寫和讀的操作順序,Relaxed僅保證原子的修改順序。
為何要分層次呢?
其實順序約束和系統(tǒng)優(yōu)化之間是一種零和博弈,約束越強,系統(tǒng)所能夠做的優(yōu)化便越少。
因此每個層次擁有效率差異,層次越低,優(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未定義同步和順序約束,所以要單獨而論。
例如load()就是Read操作,store()就是Write()操作,compare_exchange_strong就是Read-modify-Write操作。
這意味著你不能將一個Read操作的順序約束,寫到store()上。例如,若將memory_order_acquire寫到store()上,不會產(chǎn)生任何效果。
我們先來從默認的Sequential consistency開始,往往無需設置,便默認是memory_order_seq_cst,可以寫一個簡單的生產(chǎn)者-消費者函數(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}
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的關系。
它的行為和之前使用sequential consistency默認參數(shù)的自旋鎖一樣,不過要更加輕便高效。
test_and_set()操作其實是個Read-modify-Write操作,不過依舊可以使用acquire操作。release禁止了所有在它之前或之后的寫操作亂序,acquire禁止了所有在它之前或之后的讀操作亂序。
在兩個不同的線程之間,共同訪問同一個原子是flag,所添加的順序約束就是為了保證flag的修改順序。
我們再來看一個更清晰的例子:
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}
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)足夠說明其中所以,便不細述。
5
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????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,順序將不再有保證。
也許在read()中看到的write()操作是先y后x,那么此時read()里面的if操作便無法滿足,也就是說,++z不會被執(zhí)行。
解決方法是結合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}
std::atomic_thread_fence:同步線程之間的內(nèi)存訪問。
std::atomic_signal_fence:同步同一線程上的signal handler和code running。
我們主要學習第一個線程的fence,它會阻止特定的操作穿過柵欄,約束執(zhí)行順序。
有三種類型的fences,
Full fence:阻止兩個任意操作亂序。memory_order_seq_cst或memory_order_acq_rel。
Acquire fence:阻止讀操作亂序,memory_order_acquire。
Release fence:阻止寫操作亂序,memory_order_release。
用圖來表示為:
6
總結
本篇內(nèi)容挺復雜的,其實就包含三個方面:Atomic operations(原子操作)、Partial?ordering of operations(局部執(zhí)行順序)和Visible effects of operations(操作可見性)。
免責聲明:本文內(nèi)容由21ic獲得授權后發(fā)布,版權歸原作者所有,本平臺僅提供信息存儲服務。文章僅代表作者個人觀點,不代表本平臺立場,如有問題,請聯(lián)系我們,謝謝!