記憶體模型基礎 (Memory Model Basics)
Overview Table
| 概念 | 重點 |
|---|---|
| 物件與記憶體位置 | 每個物件至少佔一個記憶體位置 (memory location);相鄰位元欄位共享同一位置 |
| 併發與記憶體位置 | 兩執行緒無序存取同一位置且至少一個寫入 → 資料競爭 (data race) → 未定義行為 |
| 避免資料競爭 | 用互斥鎖 (mutex) 排序,或用原子操作排序 |
| 修改順序 (modification order) | 每個物件有一條全體執行緒必須認可的寫入順序 |
物件與記憶體位置
C++ 程式中的資料都由物件 (object) 構成,標準將物件定義為「儲存區域」。每個物件無論型別為何,都儲存在一個或多個記憶體位置上;每個記憶體位置要麼是一個標量型別 (scalar type) 的物件/子物件,要麼是一串相鄰位元欄位 (bit-field)。
四個必記原則:
- 每個變數都是物件,其成員變數也是物件。
- 每個物件至少佔一個記憶體位置。
- 基本型別(如
int、my_class*)無論大小,都有專屬的記憶體位置(即使相鄰或屬於陣列的一部分)。 - 相鄰位元欄位屬於同一個記憶體位置。
struct my_data { 記憶體位置分解(對應書中圖 5.1)
int i; ──→ 位置 1:i
double d; ──→ 位置 2:d
unsigned bf1:10; ─┐
int bf2:25; ─┴→ 位置 3:bf1 與 bf2(相鄰位元欄位共享一個位置)
int :0; (寬度 0 的未命名位元欄位:強制下一欄位對齊)
int bf4:9; ──→ 位置 4:bf4(因寬度 0 欄位而與 bf1/bf2 分離)
int i2; ──→ 位置 5:i2
char c1, c2; ──→ 位置 6:c1;位置 7:c2(各自獨立)
std::string s; ──→ 位置 8+:s 內部由多個記憶體位置組成
};
寬度 0 位元欄位
寬度為 0 的未命名位元欄位會強制下一個位元欄位對齊到下一個型別邊界,因此把位元欄位序列「切開」成兩個記憶體位置。這是讓兩組位元欄位能被獨立保護(不同 mutex)的手段。
併發與記憶體位置
- 兩執行緒存取不同記憶體位置:完全沒問題。
- 兩執行緒只讀同一位置:不需要保護或同步。
- 至少一個執行緒寫入同一位置且無順序約束:產生競爭條件 (race condition);若雙方都不是原子操作且未建立先行關係,即為資料競爭 (data race) → 未定義行為 (undefined behavior)。
避免資料競爭的兩種手段:
| 手段 | 原理 |
|---|---|
| 互斥鎖 | 同一 mutex 先鎖者先行,同一時間只有一個執行緒能存取該位置 |
| 原子操作 | 對同一位置的存取被賦予明確順序,把行為拉回「有定義」的範圍 |
未定義行為是 C++ 的黑洞
一旦程式含有任何未定義行為,整個程式的行為都無法預測。資料競爭是嚴重錯誤,必須不惜一切代價避免。
原子操作不會消除競爭本身
原子操作只保證每次存取不可分割、行為有定義;哪個執行緒先存取(競爭的結果)仍未指定。要控制「誰先誰後」仍需記憶體順序與同步(見 05-Memory-Model-and-Atomics/04-Memory-Ordering-Options)。
修改順序 (Modification Order)
每個 C++ 物件從初始化開始,就有一條由程式中所有執行緒共同認可的寫入序列,稱為修改順序。
- 所有執行緒對「單一物件」的修改順序看法必須一致;但不同物件之間的相對操作順序不需要全體一致(詳見 05-Memory-Model-and-Atomics/04-Memory-Ordering-Options 的寬鬆順序)。
- 一旦某執行緒看到物件的某個值,該執行緒之後的讀取必須回傳相同或更新的值(不允許「投機」讀回較舊的值);之後的寫入也必須排在修改順序的後面。
- 物件是原子型別:編譯器負責維護修改順序;非原子型別:程式設計師必須自行加上足夠的同步,否則就是資料競爭與未定義行為。
Exam/Test Patterns
| 情境/關鍵字 | 答案 |
|---|---|
| 兩執行緒同時寫同一記憶體位置、無同步 | 資料競爭 → 未定義行為 |
| 「相鄰位元欄位能否分別用不同 mutex 保護?」 | 不能,它們是同一個記憶體位置 |
| 想把位元欄位切成兩個記憶體位置 | 中間插入寬度 0 的未命名位元欄位 |
| 共享資料全程只讀 | 不需要保護或同步 |
| 「所有執行緒對單一物件的寫入順序」 | 修改順序,全體執行緒必須一致 |
| 「不同物件間的操作順序需要全體一致嗎?」 | 不需要(除非施加額外順序約束) |
| 原子操作能避免什麼 | 未定義行為(但不會消除競爭本身) |
Related Notes
- 05-Memory-Model-and-Atomics/02-Atomic-Types-and-Operations
- 05-Memory-Model-and-Atomics/03-Synchronizes-With-and-Happens-Before
- 05-Memory-Model-and-Atomics/04-Memory-Ordering-Options
- 05-Memory-Model-and-Atomics/05-Release-Sequences-and-Fences
- 05-Memory-Model-and-Atomics/Practice-Memory-Model-and-Atomics
- 03-Sharing-Data/01-Race-Conditions-and-Mutexes