需要記憶模型

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==truexy 中的垃圾或兩者。

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==2y==3。 **

請注意,編寫器和 CPU 仍然允許在寫入 x 之前寫入 y,類似地,use() 中這些變數的讀取可以按任何順序發生。