需要記憶模型
int x, y;
bool ready = false;
void init()
{
x = 2;
y = 3;
ready = true;
}
void use()
{
if (ready)
std::cout << x + y;
}
一個執行緒呼叫 init()
函式,而另一個執行緒(或訊號處理程式)呼叫 use()
函式。人們可能會認為 use()
功能會列印 5
或什麼也不做。出於以下幾個原因,情況可能並非總是如此:
-
CPU 可能會重新排序
init()
中發生的寫入,以便實際執行的程式碼可能如下所示:void init() { ready = true; x = 2; y = 3; }
-
CPU 可能會重新排序
use()
中發生的讀取,以便實際執行的程式碼可能變為:void use() { int local_x = x; int local_y = y; if (ready) std::cout << local_x + local_y; }
-
優化的 C++編譯器可能決定以類似的方式對程式重新排序。
這樣的重新排序不能改變在單執行緒中執行的程式的行為,因為執行緒不能將呼叫交錯到 init()
和 use()
。另一方面,在多執行緒設定中,一個執行緒可能會看到另一個執行緒執行的部分寫操作,其中 use()
可能會看到 ready==true
和 x
或 y
中的垃圾或兩者。
C++記憶體模型允許程式設計師指定允許哪些重新排序操作,哪些不允許,以便多執行緒程式也能夠按預期執行。上面的例子可以用執行緒安全的方式重寫,如下所示:
int x, y;
std::atomic<bool> ready{false};
void init()
{
x = 2;
y = 3;
ready.store(true, std::memory_order_release);
}
void use()
{
if (ready.load(std::memory_order_acquire))
std::cout << x + y;
}
這裡 init()
執行原子儲存釋放操作。這不僅將值 true
儲存到 ready
中,而且還告訴編譯器它不能在之前排序的寫操作之前移動此操作。
use()
函式執行原子載入 - 獲取操作。它讀取 ready
的當前值,並禁止編譯器在原子載入獲取之前放置在其**之後排序的讀操作。 ** **
這些原子操作還會使編譯器放置所需的任何硬體指令,以通知 CPU 避免不必要的重新排序。
因為原子儲存釋放與原子載入獲取位於相同的記憶體位置,所以記憶體模型規定如果載入獲取操作看到儲存釋放操作寫入的值,那麼 init()
的執行緒之前執行的所有寫入對於 use()
的執行緒在其載入獲取後執行的載入,該儲存釋放將可見。那就是如果 use()
看到 ready==true
,那麼它肯定會看到 x==2
和 y==3
。 **
請注意,編寫器和 CPU 仍然允許在寫入 x
之前寫入 y
,類似地,use()
中這些變數的讀取可以按任何順序發生。