前文我們通過原子操作實戰實現了無鎖隊列,今天完善一下無鎖的原子操作剩余的知識,包括Relaese和Acquire內存序在什么情況下是存在危險的,以及我們可以利用柵欄機制實現同步等等。
線程可見順序
- 我們提到過除了memory_order_seq_cst順序,其他的順序都不能保證原子變量修改的值在其他多線程中看到的順序是一致的。
但是可以通過同步機制保證一個線程對原子變量的修改對另一個原子變量可見。通過“Syncronizes With” 的方式達到先行的效果。
但是我們說的先行是指 “A Syncronizes With B ”, 如果A 的結果被B讀取,則A 先行于B。
有時候我們線程1對A的store操作采用release內存序,而線程2對B的load采用acquire內存序,并不能保證A 一定比 B先執行。因為兩個線程并行執行無法確定先后順序,我們指的先行不過是說如果B讀取了A操作的結果,則稱A先行于B。
我們看下面的一段案例
#include <iostream>
#include <atomic>
#include <thread>
#include <cassert>
std::atomic<bool> x, y;
std::atomic<int> z;
void write_x()
{x.store(true, std::memory_order_release); //1
}
void write_y()
{y.store(true, std::memory_order_release); //2
}
void read_x_then_y()
{while (!x.load(std::memory_order_acquire));if (y.load(std::memory_order_acquire)) //3++z;
}
void read_y_then_x()
{while (!y.load(std::memory_order_acquire));if (x.load(std::memory_order_acquire)) //4++z;
}
// 我們寫一個函數測試,函數TestAR中初始化x和y為false, 啟動4個線程a,b,c,d,分別執行write_x, write_y, read_x_then_y, read_y_then_x.
void TestAR()
{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); //5std::cout << "z value is " << z.load() << std::endl;
}
有的讀者可能會覺5處的斷言不會被觸發,他們認為c和d肯定會有一個線程對z執行++操作。他們的思路是這樣的。
1 如果c線程執行read_x_then_y沒有對z執行加加操作,那么說明c線程讀取的x值為true, y值為false。
2 之后d線程讀取時,如果保證執行到4處說明y為true,等d線程執行4處代碼時x必然為true。
3 他們的理解是如果x先被store為true,y后被store為true,c線程看到y為false時x已經為true了,那么d線程y為true時x也早就為true了,所以z一定會執行加加操作。
上述理解是不正確的,我們提到過即便是releas和acquire順序也不能保證多個線程看到的一個變量的值是一致的,更不能保證看到的多個變量的值是一致的。
變量x和y的載入操作3和4有可能都讀取false值(與寬松次序的情況一樣),因此有可能令斷言觸發錯誤。變量x和y分別由不同線程寫出,所以兩個釋放操作都不會影響到對方線程。
看下圖
即線程a執行了,但是線程d沒有看見
線程b執行了,但是線程c沒有看見
柵欄
有時候我們可以通過柵欄保證指令編排順序。
看下面一段代碼
#include <atomic>
#include <thread>
#include <assert.h>
std::atomic<bool> x,y;
std::atomic<int> z;
void write_x_then_y()
{x.store(true,std::memory_order_relaxed); // 1y.store(true,std::memory_order_relaxed); // 2
}
void read_y_then_x()
{while(!y.load(std::memory_order_relaxed)); // 3if(x.load(std::memory_order_relaxed)) // 4++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); //5
}