同步發生與先行發生 (Synchronizes-With and Happens-Before)

Overview Table

關係 適用範圍 一句話定義
sequenced-before(序前) 單一執行緒內 源碼中 A 排在 B 之前 → A 序前於 B
synchronizes-with(同步發生) 跨執行緒、只在原子操作間 原子讀 R 讀到原子寫 W(或其後續)寫入的值 → W 同步發生於 R
inter-thread happens-before(執行緒間先行) 跨執行緒 由同步發生 + 序前組合、可傳遞
happens-before(先行發生) 全域 sequenced-before 或 inter-thread happens-before
strongly-happens-before(強先行發生) 全域 同上,但排除 memory_order_consume 標記的操作

動機場景:以原子旗標排序非原子資料

兩個執行緒:一個填資料、一個讀資料。用 std::atomic<bool> data_ready 當旗標,讀者在旗標設置前不讀資料(代碼 5.2):

std::vector<int> data;               // 非原子!
std::atomic<bool> data_ready(false);

void writer_thread() {
  data.push_back(42);                // ③ 寫資料
  data_ready = true;                 // ④ 設旗標
}
void reader_thread() {
  while (!data_ready.load());        // ① 等旗標
  std::cout << data[0];              // ② 讀資料
}
writer_thread                       reader_thread
-------------                       -------------
③ data.push_back(42)
   │ sequenced-before
④ data_ready = true (store)
   └────── synchronizes-with ────→ ① data_ready.load() == true
                                      │ sequenced-before
                                    ② 讀 data[0]

傳遞得出:③ happens-before ② ⇒ 對非原子 data 的存取沒有資料競爭

同步發生 (Synchronizes-With)

只發生在原子型別的操作之間(或內部使用原子操作的機制,如 mutex——見 05-Memory-Model-and-Atomics/05-Release-Sequences-and-Fences)。

基本定義:對變數 x 的一個適當標記的原子 W,同步發生於一個適當標記、且讀到下列值之一的原子:

  1. W 本身寫入的值;
  2. W 之後,同一執行緒對 x 的後續原子寫所寫入的值;
  3. 任意執行緒對 x 的一連串原子讀-改-寫操作(如 fetch_add()compare_exchange_weak())所形成序列中的值(→ 釋放序列,詳見 05-Memory-Model-and-Atomics/05-Release-Sequences-and-Fences)。
「適當標記」是什麼?

原子操作預設(memory_order_seq_cst)就是適當標記——所以「執行緒 A 存值、執行緒 B 讀到該值」預設即建立同步發生。所有微妙之處都在其他記憶體順序選項上(見 05-Memory-Model-and-Atomics/04-Memory-Ordering-Options:release store 配 acquire load 才同步;relaxed 不同步)。

先行發生 (Happens-Before) 與 Sequenced-Before

先行發生是程式操作順序的基本建構塊:指定「哪個操作能看見哪個操作的效果」。

foo(get_num(), get_num()); // 兩次 get_num() 呼叫順序未指定
// 可能輸出 "1,2" 也可能 "2,1"(代碼 5.3)
「同時發生」通常沒有先行關係

一條語句中的引數求值彼此之間沒有 sequenced-before(逗號運算子、或把表達式結果作為另一表達式的引數等情況除外)。無序 → 無先行發生。

執行緒間先行 (Inter-Thread Happens-Before) 與傳遞性

新意在於跨執行緒的組合規則(依賴同步發生):

規則 內容
基礎 A 同步發生於 B(不同執行緒) → A 執行緒間先行於 B
傳遞 A 執行緒間先行於 B,且 B 執行緒間先行於 C → A 執行緒間先行於 C
與序前結合 (1) A 序前於 B,且 B 執行緒間先行於 C → A 執行緒間先行於 C
與序前結合 (2) A 同步發生於 B,且 B 序前於 C → A 執行緒間先行於 C

強先行發生 (Strongly-Happens-Before)


Exam/Test Patterns

情境/關鍵字 答案
「synchronizes-with 發生在什麼之間?」 適當標記的原子操作之間(含原子讀到 RMW 序列中的值)
A 序前於 B、B 執行緒間先行於 C A 執行緒間先行於 C(可與序前結合、可傳遞)
foo(get_num(), get_num()) 的求值順序 未指定,兩引數間無 sequenced-before
「單執行緒內先行發生的基礎」 sequenced-before(源碼順序)
原子旗標 + 非原子資料的正確性論證 store/load 同步發生 + 兩端 sequenced-before → 資料寫先行於資料讀
happens-before vs strongly-happens-before 差在 memory_order_consume(consume 不算強先行)
一串修改後如何讓他執行緒全部看見 尾端一次同步即可(先行發生的傳遞性)