C 併發程式設計(六) 記憶體順序

2021-09-11 19:48:15 字數 3326 閱讀 8568

有六個記憶體順序選項可應用於對原子型別的操作:memory_order_relaxed, memory_order_consume, memory_order_acquire, memory_order_release, memory_order_acq_rel, 以及memory_order_seq_cst。除非你為特定的操作指定乙個順序選項,否則記憶體順序選項對於所有原子型別預設都是memory_order_seq_cst。

6個記憶體順序可以分為3類:

自由順序(memory_order_relaxed)

獲取-釋放順序(memory_order_consume, memory_order_acquire, memory_order_release和memory_order_acq_rel)

排序一致順序(memory_order_seq_cst),

在原子型別上的操作以自由序列執行,沒有任何同步關係,僅對此操作要求原子性。例如,在某一線程中,先寫入a,再寫入b。但是在多核處理器中觀測到的順序可能是先寫入b,再寫入a。自由記憶體順序對於不同變數可以自由重排序。

這是因為不同的cpu快取和內部緩衝區,在同樣的儲存空間中可以儲存不同的值。對於非一致排序操作,執行緒沒有必要去保證一致性。

#include #include #include std::atomicx,y;

std::atomicz;

void write_x_then_y()

void read_y_then_x()

int main()

上述**,z.load()!=0有可能會返回false。在b執行緒中,多核處理器觀測到的順序是隨機的。b執行緒中的觀測到的變數的並不會與執行緒a中的變數做同步,沒有任何順序要求。

使用memory_order_release的原子操作,當前執行緒的讀寫操作都不能重排到此操作之後。例如,某一線程先寫入a,再寫入b,再以memeory_order_release操作寫入c,再寫入d。在多核處理器中觀測到的順序ab只能在c之前,不能出現c寫入之後,a或b再寫入的情況。但是,可能出現d重排到c之前的情況。

memory_order_release用於發布資料,放在寫操作的最後。

使用memory_order_acquire的原子操作,當前執行緒的讀寫操作都不能重排到此操作之前。例如,某一線程先讀取a,再讀取b,再以memeory_order_acquire操作讀取c,再讀取d。在多核處理器中觀測到的順序d只能在c之前,不能出現先讀取d,最後讀取c的情況。但是,可能出現a或b重排到c之後的情況。

memory_order_acquire用於獲取資料,放在讀操作的最開始 。

#include #include #include std::atomicx,y;

std::atomicz;

void write_x_then_y()

void read_y_then_x()

int main()

上述**是使用「釋放-獲取"模型對「自由」模型的改進。z.load() != 0 返回的一定是true。首先,a執行緒中,y使用memory_order_release釋放記憶體順序,在多核處理器觀測到的順序,x的賦值肯定會位於y之前。b執行緒中,y的獲取操作是同步操作,x的訪問順序必定在y之後,觀測到的x的訪問值一定為true。

「獲取」與「釋放」一般會成對出現,用來同步執行緒。

memory_order_acq_rel帶此記憶體順序的讀-改-寫操作既是獲得載入又是釋放操作。沒有操作能夠從此操作之後被重排到此操作之前,也沒有操作能夠從此操作之前被重排到此操作之後。

std::atomicsync(0);

void thread_1()

void thread_2()

void thread_3()

上述**,使用memory_order_acq_rel來實現3個執行緒的同步。thread1執行寫入功能,thread2執行讀取功能。3個執行緒的執行順序是確定的。compare_exchange_strong,當*this值與expected相同時,會將2賦值*this,返回true,不同時,將*this賦值expected,返回flase。

memory_order_consume只會對其標識的物件保證該物件儲存先行於那些需要載入該物件的操作。

struct x

;std::atomicp;

std::atomica;

void create_x()

void use_x()

int main()

x->i ==42,和x-> == "hello"會被確保已被賦值。但是a的值卻是不確定的。載入p的操作標記為memory_order_consume,這就意味著儲存p僅先行那些需要載入p的操作,對於a是沒有保障的。

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::atomicx,y;

std::atomicz;

void write_x()

void write_y()

void read_x_then_y()

void read_y_then_x()

int main()

z.load() != 0 一定會為true。memory_order_seq_cst的語義會為所有操作都標記為memory_order_seq_cst建立乙個單獨全序。執行緒c和d總會有乙個執行z++,x和y的賦值順序,不管誰先誰後,在所有執行緒的眼中順序都是確定的。

C 併發程式設計 記憶體柵欄

因為這類操作就像畫了一條任何 都無法跨越的線一樣,所以柵欄操作通常也被稱為 記憶體柵欄 memory barriers 使用柵欄的一般想法是 當乙個獲取操作能看到釋放柵欄操作後的儲存結果,那麼這個柵欄就與獲取操作同步 並且,當載入操作在獲取柵欄操作前,看到乙個釋放操作的結果,那麼這個釋放操作同步於獲...

C 併發程式設計 記憶體模型

c 程式中的所有資料都是由物件 objects 構成,乙個物件都會儲存在乙個或多個記憶體位置上。位域 雖然相鄰位域中是不同的物件,但仍視其為相同的記憶體位置 四個原則 記憶體位置和併發關係 當兩個執行緒訪問不同 separate 的記憶體位置時,不會存在任何問題 當兩個執行緒訪問同一 same 個記...

java併發程式設計 六

併發工具類 lk最近學習了併發工具類的知識,總結一下它們的用法 1.countdownlatch 2.cyclicbarrier 3.semaphore 4.exchanger 使用完countdownlatch之後,覺得它適合應用於讓其它執行緒去各自執行完,然後main執行緒開始執行的場合 自己理...