原子操作內(nèi)存序

[TOC]

參考

1. C++11多線程-內(nèi)存模型
2. c++并發(fā)編程1.內(nèi)存序
3. 淺談Memory Reordering
4. C++11中的內(nèi)存模型下篇 - C++11支持的幾種內(nèi)存模型
5. C++11中的內(nèi)存模型上篇 - 內(nèi)存模型基礎

前言

有三種情況她肯,可能導致亂序執(zhí)行:編譯器優(yōu)化、CPU亂序、緩存不一致居夹。進而導致多線程情況下出現(xiàn)問題陨享。[1,3,4]

c++11引入了atomic類型之后,大大方便了原子變量的使用吭狡,但是原子變量的內(nèi)存序有好幾種囤锉,這又引入了讓人難以理解的內(nèi)容。
內(nèi)存序分為三類六種

  • relaxed(松弛的內(nèi)存序)
  • sequential_consistency(內(nèi)存一致序)
  • acquire-release(獲取-釋放一致性)

relaxed

//test.cpp
#include <thread>
#include <atomic>
#include <assert.h>

std::atomic<bool> x{false},y{false};
std::atomic<int> z{0};

void write_x_then_y() {
    x.store(true,std::memory_order_relaxed);   //1
    y.store(true,std::memory_order_relaxed);   //2
}

void read_y_then_x() {
    while(!y.load(std::memory_order_relaxed));  //3
    if(x.load(std::memory_order_relaxed))     //4
        ++z;
}

int main() {
    std::thread b(read_y_then_x);
    std::thread a(write_x_then_y);
    a.join();
    b.join();
    if (z.load() != 0) return 0; else return 1;
}
# test.sh
#!/bin/bash
for ((i=0;i<1;)); do
    ./a.out
    if [ "$?" == "1" ];then
        break
    fi
done

g++ -std=c++17 -pthread -O2 test.cpp編譯以上代碼涡拘,time sh test.sh執(zhí)行代碼玲躯。

如果出現(xiàn) 2 -> 3 -> 4 -> 1這樣的執(zhí)行次序,那么就會出現(xiàn)z == 0這種錯誤情況鳄乏。

<font color=red>注跷车,不過我跑了一晚上,并沒有復現(xiàn)這個結(jié)果</font>

那么relaxed用于何處呢橱野?對于計數(shù)這種場景朽缴,就可以使用relaxed來最大化性能。

#include <cassert>
#include <vector>
#include <thread>
#include <atomic>

std::atomic<int> count{0};
void f() {
    for (int n = 0; n < 1000; ++n) {
        count.fetch_add(1, std::memory_order_relaxed);
    }
}
int main() {
    std::thread threads[10];
    for (std::thread &thr: threads) {
        thr = std::thread(f);
    }
    for (auto &thr : v) {
        thr.join();
    }
    assert(cnt == 10000); // 永遠不會失敗
    return 0;
}

release-acquire

針對relaxed的例子水援,如果改成如下的代碼就可以避免z == 0這種錯誤情況密强。

#include <thread>
#include <atomic>
#include <assert.h>

std::atomic<bool> x{false},y{false};
std::atomic<int> z{0};

void write_x_then_y() {
    x.store(true,std::memory_order_relaxed);   //1
    y.store(true,std::memory_order_release);   //2
}

void read_y_then_x() {
    while(!y.load(std::memory_order_acquire));  //3
    if(x.load(std::memory_order_relaxed))     //4
        ++z;
}

int main() {
    std::thread b(read_y_then_x);
    std::thread a(write_x_then_y);
    a.join();
    b.join();
    if (z.load() != 0) return 0; else return 1;
}

他會保證1發(fā)生在2前茅郎,4發(fā)生在3后,同時3一定發(fā)生在2后或渤,那么z == 0不會發(fā)生系冗。

如下圖分析

image.png
  • 初始條件為x = y = false。
  • 在write_x_then_y線程中薪鹦,先執(zhí)行對x的寫操作掌敬,再執(zhí)行對y的寫操作,由于兩者在同一個線程中池磁,所以即便針對x的修改操作使用relaxed模型奔害,修改x也一定在修改y之前執(zhí)行。
  • 在write_x_then_y線程中地熄,對y的load操作使用了acquire模型华临,而在線程write_x_then_y中針對變量y的讀操作使用release模型,因此保證了是先執(zhí)行write_x_then_y函數(shù)才到read_y_then_x的針對變量y的load操作端考。
  • 因此最終的執(zhí)行順序如上圖所示雅潭,此時不可能出現(xiàn)z=0的情況。

從以上的分析可以看出却特,針對同一個變量的release-acquire操作寻馏,更多時候扮演了一種“線程間使用某一變量的同步”作用,由于有了這個語義的保證核偿,做到了線程間操作的先后順序保證(inter-thread happens-before)。

可以簡單記作release為寫不后顽染,acquire為讀不前漾岳。[2]

release-consume

官方不推薦,此處不進行詳細描述粉寞。簡單說尼荆,release-acquire會把不相關(guān)的變量存取都進行保序,release-consume只會對有依賴的變量保序唧垦,進而提高效率捅儒,同時也使代碼更容易引入bug。

sequential consistency

這是最嚴格的級別振亮,也是性能最差的級別巧还,同時也是默認的級別。
如下列:

#include <thread>
#include <atomic>
#include <cassert>
 
std::atomic<bool> x = {false};
std::atomic<bool> y = {false};
std::atomic<int> z = {0};
 
void write_x() {
    x.store(true, std::memory_order_seq_cst);  // 1
}
 
void write_y() {
    y.store(true, std::memory_order_seq_cst);  // 2
}
 
void read_x_then_y() {
    while (!x.load(std::memory_order_seq_cst));  // 3
    if (y.load(std::memory_order_seq_cst)) {  // 4
        ++z;
    }
}
 
void read_y_then_x() {
    while (!y.load(std::memory_order_seq_cst));  // 5
    if (x.load(std::memory_order_seq_cst)) {   // 6
        ++z;
    }
}
 
int main() {
    std::thread a(write_x);
    std::thread b(write_y);
    std::thread c(read_x_then_y); // thread c
    std::thread d(read_y_then_x); // thread d
    a.join(); b.join(); c.join(); d.join();
    // failed to assert without memory_order_seq_cst
    assert(z.load() != 0);
}

如果使用release-acquire坊秸,那么線程c可能看到的是2 -> 1這個執(zhí)行順序麸祷,但是線程d可能看到的是1->2這個執(zhí)行順序,進而導致z == 0褒搔。

如下圖分析:


image.png
  • 初始條件為x = y = false阶牍。
  • 由于在read_x_and_y線程中喷面,對x的load操作使用了acquire模型,因此保證了是先執(zhí)行write_x函數(shù)才到這一步的走孽;同理先執(zhí)行write_y才到read_y_and_x中針對y的load操作惧辈。
  • 然而即便如此,也可能出現(xiàn)在read_x_then_y中針對y的load操作在y的store操作之前完成磕瓷,因為y.store操作與此之間沒有先后順序關(guān)系盒齿;同理也不能保證x一定讀到true值,因此到程序結(jié)束是就出現(xiàn)了z = 0的情況生宛。

從上面的分析可以看到县昂,即便在這里使用了release-acquire模型,仍然沒有保證z==0陷舅,其原因在于:最開始針對x倒彰、y兩個變量的寫操作是分別在write_x和write_y線程中進行的,不能保證兩者執(zhí)行的順序?qū)е隆?/p>

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末莱睁,一起剝皮案震驚了整個濱河市待讳,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌仰剿,老刑警劉巖创淡,帶你破解...
    沈念sama閱讀 216,651評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異南吮,居然都是意外死亡琳彩,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評論 3 392
  • 文/潘曉璐 我一進店門部凑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來露乏,“玉大人,你說我怎么就攤上這事涂邀∥练拢” “怎么了?”我有些...
    開封第一講書人閱讀 162,931評論 0 353
  • 文/不壞的土叔 我叫張陵比勉,是天一觀的道長劳较。 經(jīng)常有香客問我,道長浩聋,這世上最難降的妖魔是什么观蜗? 我笑而不...
    開封第一講書人閱讀 58,218評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮衣洁,結(jié)果婚禮上嫂便,老公的妹妹穿的比我還像新娘。我一直安慰自己闸与,他們只是感情好毙替,可當我...
    茶點故事閱讀 67,234評論 6 388
  • 文/花漫 我一把揭開白布岸售。 她就那樣靜靜地躺著,像睡著了一般厂画。 火紅的嫁衣襯著肌膚如雪凸丸。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,198評論 1 299
  • 那天袱院,我揣著相機與錄音屎慢,去河邊找鬼。 笑死忽洛,一個胖子當著我的面吹牛腻惠,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播欲虚,決...
    沈念sama閱讀 40,084評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼集灌,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了复哆?” 一聲冷哼從身側(cè)響起欣喧,我...
    開封第一講書人閱讀 38,926評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎梯找,沒想到半個月后唆阿,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,341評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡锈锤,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,563評論 2 333
  • 正文 我和宋清朗相戀三年驯鳖,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片久免。...
    茶點故事閱讀 39,731評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡臼隔,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出妄壶,到底是詐尸還是另有隱情,我是刑警寧澤寄狼,帶...
    沈念sama閱讀 35,430評論 5 343
  • 正文 年R本政府宣布丁寄,位于F島的核電站,受9級特大地震影響泊愧,放射性物質(zhì)發(fā)生泄漏伊磺。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,036評論 3 326
  • 文/蒙蒙 一删咱、第九天 我趴在偏房一處隱蔽的房頂上張望屑埋。 院中可真熱鬧,春花似錦痰滋、人聲如沸摘能。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽团搞。三九已至严望,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間逻恐,已是汗流浹背像吻。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留复隆,地道東北人拨匆。 一個月前我還...
    沈念sama閱讀 47,743評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像挽拂,于是被迫代替她去往敵國和親惭每。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,629評論 2 354