C 的 6 种内存顺序,你都知道吗?
原子操作的內存順序
有六個內存順序選項可應用于對原子類型的操作:
1. memory_order_relaxed
2. memory_order_consume
3. memory_order_acquire
4. memory_order_release
5. memory_order_acq_rel
6. memory_order_seq_cst。
除非你為特定的操作指定一個順序選項,否則內存順序選項對于所有原子類型默認都是memory_order_seq_cst。
6個內存順序可以分為3類:
1. 自由順序
(memory_order_relaxed)
2.獲取-釋放順序
(memory_order_consume, memory_order_acquire, memory_order_release和memory_order_acq_rel)
3.排序一致順序
(memory_order_seq_cst)
1、std::memory_order_relaxed “自由”內存順序
在原子類型上的操作以自由序列執行,沒有任何同步關系,僅對此操作要求原子性。例如,在某一線程中,先寫入A,再寫入B。但是在多核處理器中觀測到的順序可能是先寫入B,再寫入A。自由內存順序對于不同變量可以自由重排序。
這是因為不同的CPU緩存和內部緩沖區,在同樣的存儲空間中可以存儲不同的值。對于非一致排序操作,線程沒有必要去保證一致性。
#include?#include?#include??std::atomic?x,y;std::atomic?z;?void?write_x_then_y(){??x.store(true,std::memory_order_relaxed);????y.store(true,std::memory_order_relaxed);??}void?read_y_then_x(){??while(!y.load(std::memory_order_relaxed));????if(x.load(std::memory_order_relaxed))?????? z;}int?main(){??x=false;??y=false;??z=0;??std::thread?a(write_x_then_y);??std::thread?b(read_y_then_x);??a.join();??b.join();??assert(z.load()!=0);??}上述代碼,z.load()!=0有可能會返回false。在b線程中,多核處理器觀測到的順序是隨機的。b線程中的觀測到的變量的并不會與線程a中的變量做同步,沒有任何順序要求。
2、std::memory_order_release “釋放”內存順序
使用memory_order_release的原子操作,當前線程的讀寫操作都不能重排到此操作之后。例如,某一線程先寫入A,再寫入B,再以memeory_order_release操作寫入C,再寫入D。在多核處理器中觀測到的順序AB只能在C之前,不能出現C寫入之后,A或B再寫入的情況。但是,可能出現D重排到C之前的情況。
memory_order_release用于發布數據,放在寫操作的最后。
3、std::memory_order_acquire “獲取”內存順序
使用memory_order_acquire的原子操作,當前線程的讀寫操作都不能重排到此操作之前。例如,某一線程先讀取A,再讀取B,再以memeory_order_acquire操作讀取C,再讀取D。在多核處理器中觀測到的順序D只能在C之前,不能出現先讀取D,最后讀取C的情況。但是,可能出現A或B重排到C之后的情況。
memory_order_acquire用于獲取數據,放在讀操作的最開始 。
#include?#include?#include??std::atomic?x,y;std::atomic?z;?void?write_x_then_y(){??x.store(true,std::memory_order_relaxed);????y.store(true,std::memory_order_release);??}void?read_y_then_x(){??while(!y.load(std::memory_order_acquire));??//?自旋,等待y被設置為true??if(x.load(std::memory_order_relaxed))?????? z;}int?main(){??x=false;??y=false;??z=0;??std::thread?a(write_x_then_y);??std::thread?b(read_y_then_x);??a.join();??b.join();??assert(z.load()!=0);}上述代碼是使用“釋放-獲取"模型對“自由”模型的改進。z.load() != 0 返回的一定是true。首先,a線程中,y使用memory_order_release釋放內存順序,在多核處理器觀測到的順序,x的賦值肯定會位于y之前。b線程中,y的獲取操作是同步操作,x的訪問順序必定在y之后,觀測到的x的訪問值一定為true。
“獲取”與“釋放”一般會成對出現,用來同步線程。
4、std::memory_order_acq_rel? "獲取釋放"內存順序
memory_order_acq_rel帶此內存順序的讀-改-寫操作既是獲得加載又是釋放操作。沒有操作能夠從此操作之后被重排到此操作之前,也沒有操作能夠從此操作之前被重排到此操作之后。
std::atomic?sync(0);void?thread_1(){??//?...??sync.store(1,std::memory_order_release);}?void?thread_2(){??int?expected=1;??while(!sync.compare_exchange_strong(expected,2,??????????????std::memory_order_acq_rel))????expected=1;}void?thread_3(){??while(sync.load(std::memory_order_acquire)<2);??//?...}上述代碼,使用memory_order_acq_rel來實現3個線程的同步。thread1執行寫入功能,thread2執行讀取功能。3個線程的執行順序是確定的。compare_exchange_strong,當*this值與expected相同時,會將2賦值*this,返回true,不同時,將*this賦值expected,返回flase。
5、std::memory_order_consume 依賴于數據的內存順序
memory_order_consume只會對其標識的對象保證該對象存儲先行于那些需要加載該對象的操作。
struct?X{int?i;std::string?s;};?std::atomic?p;std::atomic?a;?void?create_x(){??X*?x=new?X;??x->i=42;??x->s="hello";??a.store(99,std::memory_order_relaxed);????p.store(x,std::memory_order_release);??}?void?use_x(){??X*?x;??while(!(x=p.load(std::memory_order_consume)))??????std::this_thread::sleep(std::chrono::microseconds(1));??assert(x->i==42);????assert(x->s=="hello");????assert(a.load(std::memory_order_relaxed)==99);??/}?int?main(){??std::thread?t1(create_x);??std::thread?t2(use_x);??t1.join();??t2.join();}x->i ==42,和x-> == "hello"會被確保已被賦值。但是a的值卻是不確定的。加載p的操作標記為memory_order_consume,這就意味著存儲p僅先行那些需要加載p的操作,對于a是沒有保障的。
6、std::memory_order_seq_cst?“順序一致”內存順序
memory_order_seq_cst比std::memory_order_acq_rel更為嚴格。memory_order_seq_cst不僅是一個"獲取釋放"內存順序,它還會對所有擁有此標簽的內存操作建立一個單獨全序。memory_order_acq_rel的順序保障,是要基于同一個原子變量的。memory_order_acq_rel使用了兩個不同的原子變量x1, x2,那在x1之前的讀寫,重排到x2之后,是完全可能的,在x1之后的讀寫,重排到x2之前,也是被允許的。然而,如果兩個原子變量x1,x2,是基于memory_order_seq_cst在操作,那么即使是x1之前的讀寫,也不能被重排到x2之后,x1之后的讀寫,也不能重排到x2之前,也就說,如果都用memory_order_seq_cst,那么程序代碼順序(Program Order)就將會是你在多個線程上都實際觀察到的順序(Observed Order)。
順序一致是最簡單、直觀的序列,但是它也是最昂貴的內存序列,它需要對所有線程進行全局同步,比其他的順序造成更多的消耗。因為保證一致順序,需要添加額外的指令。
#include?#include?#include??std::atomic?x,y;std::atomic?z;?void?write_x(){??x.store(true,std::memory_order_seq_cst);?}?void?write_y(){??y.store(true,std::memory_order_seq_cst);??}void?read_x_then_y(){??while(!x.load(std::memory_order_seq_cst));??if(y.load(std::memory_order_seq_cst))?????? z;}void?read_y_then_x(){??while(!y.load(std::memory_order_seq_cst));??if(x.load(std::memory_order_seq_cst))?????? z;}int?main(){??x=false;??y=false;??z=0;??std::thread?a(write_x);??std::thread?b(write_y);??std::thread?c(read_x_then_y);??std::thread?d(read_y_then_x);??a.join();??b.join();??c.join();??d.join();??assert(z.load()!=0);??}z.load() != 0 一定會為true。memory_order_seq_cst的語義會為所有操作都標記為memory_order_seq_cst建立一個單獨全序。線程c和d總會有一個執行z ,x和y的賦值順序,不管誰先誰后,在所有線程的眼中順序都是確定的。
來源:鹽焗咸魚
https://blog.csdn.net/qq_33215865/article/details/88089927
總結
以上是生活随笔為你收集整理的C 的 6 种内存顺序,你都知道吗?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 电脑显卡坏了能不能修(电脑显卡坏了可以修
- 下一篇: 如何优雅地实现 C 编译期静态反射