標籤:

為什麼`atomic::fetch_add()`可以 relaxed memory order?

std::atomic& a(0);

void thread1()
{
int x = a.fetch_add(1, std::memory_order_relaxed);
std::cout &<&< x &<&< std::endl; } void thread2() { int x = a.fetch_add(1, std::memory_order_relaxed); std::cout &<&< x &<&< std::endl; } int main() { std::thread t1(thread1); std::thread t2(thread2); t1.join(); t2.join(); // 此時 a 為**一定** 2. return 0; }

代碼如上;

按照我的理解,標準中指定 relaxed operation 只是一個原子操作,並不是一個同步操作.所以有可能`thread1()`,`thread2()`在執行`fetch_add()`時看到的`a`的值均為0.

之所以說`a`一定為`2`,是在這裡:http://en.cppreference.com/w/cpp/atomic/atomic/fetch_add 看到的.

謝謝.


C++里的memory order是用來指定原子操作(atomic operation)和同線程內其他非原子操作之間的執行順序的。

std::memory_order specifies how regular, non-atomic memory accesses are to be ordered around an atomic operation.

因此,對於兩個並發的原子操作,不論指定的是哪種memory order,它們的執行肯定是原子的。但是考慮下面修改後的例子,memory order就有意義了。當引入另外一個全局共享變數b以後,線程1在a.fetch_add之前把b寫入5,線程2在讀取到x為1的情況下(也就是說線程2的fetch_add發生在線程1的fetch_add之後)讀出的b值不一定等於5。這是因為現代處理器對沒有依賴的讀寫操作可能會進行亂序執行。不強制指定memory order的話,沒有辦法保證b=5這個寫入操作確確實實發生在a.fetch_add之前。C++的memory order就是為了在這一類情況下給程序員控制內存讀寫順序的。

std::atomic& a(0);
int b(0);

void thread1()
{
b = 5;
int x = a.fetch_add(1, std::memory_order_relexed);
}

void thread2()
{
int x = a.fetch_add(1, std::memory_order_relaxed);
int read_b = b;
if(x == 1)
{
std::cout &<&< b &<&< std::endl; //b不一定為5! } } int main() { std::thread t1(thread1); std::thread t2(thread2); t1.join(); t2.join(); // 此時 a 為**一定** 2. return 0; }

如果要保證線程2在讀到x為1時必然能觀察到線程1寫入的b值,那我們就要用到如下的內存順序參數。memory_order_release確保了線程1在進行原子操作前所有寫在前面的非原子內存寫入都已經完成了, memory_order_acquire確保了線程2不能把任何寫在原子操作之後的非原子內存讀取亂序到原子操作之前執行。這樣的話線程2讀到x為1時,必然讀到b為5。

std::atomic& a(0);
int b(0);

void thread1()
{
b = 5;
int x = a.fetch_add(1, std::memory_order_release);
}

void thread2()
{
int x = a.fetch_add(1, std::memory_order_acquire);
int read_b = b;
if(x == 1)
{
std::cout &<&< read_b &<&< std::endl; //b一定為5! } } int main() { std::thread t1(thread1); std::thread t2(thread2); t1.join(); t2.join(); // 此時 a 為**一定** 2. return 0; }

關於C++其他的內存順序,詳細的可以進一步參考std::memory_order。另外,在C++11內存模型出來以前,大家是手寫memory barrier來強制內存讀寫順序的,詳見Memory barrier。


fetch_add這個原子操作本身自帶memory_order_seq_cst語義,在X86上它用了LOCK前綴。


既然 fetch_add 是原子操作,a 當然一定是2。

int x = a.fetch_add(1, std::memory_order_relaxed);

編譯出來就是兩條指令:

movl $1, %edx
lock xaddl %edx, (%rax)

其中 lock xaddl 是原子的,%rax 是 a 的地址,執行完之後 %edx 是 (%rax) 的舊值,也就是 x 。

https://en.wikipedia.org/wiki/Fetch-and-add#x86_implementation


你的說法違反了原子性。


relaxed memory order代表不需要和另一線程relaxed memory order指令同步,提高性能又不影響最後的結果.在x86上沒卵用.


難道原子的 read-modify-write 操作本身自帶 memory_order_seq_cst 語義...


然而那個鏈接里的cout的時候讀出數據的時候會有同步操作吧


推薦閱讀:

make 多線程編譯會出錯么?
CLion 鏈接庫?如 lpthread 怎麼設置?
剛學c++多線程,需要從哪些方面入手,有推薦的教程或書籍嗎?
多線程引用計數如何釋放?
C++中如何將函數調用轉發至另一個線程?

TAG:多線程 | C11 |