Wiki
std::atomic
本文由簡體中文內容確定性轉換,並受版本化術語表保護。
std::atomic<T> 用來對一個共享對象執行原子操作,定義在:
#include <atomic>
“原子”表示某個操作在併發觀察下不可被拆成一半看到。 例如一個原子自增不會出現另一個線程只看到“讀取完成但寫回還沒完成”的中間狀態。
但要特別注意:
std::atomic保證原子語義,不等於保證底層實現一定 lock-free。
是否真正無鎖取決於類型、平台和標準庫實現。
學習 atomic 時不要一上來就想“無鎖算法”。
更適合初學的順序是:
普通变量为什么会出错
↓
单个变量怎样做到原子读写
↓
读-改-写怎样作为一个整体完成
↓
多个线程之间怎样发布和观察状态
↓
什么时候 atomic 不够,需要 mutex
也就是説,std::atomic 首先是一個正確性工具,其次才可能是性能工具。
1.atomic 解決什麼問題
1.1.為什麼普通變量不夠
下面的代碼存在數據競爭:
int counter = 0;
void add_many()
{
for (int i = 0; i < 100000; ++i)
{
++counter;
}
}
如果多個線程同時執行 ++counter,結果並不可靠。
原因不是 int 本身不能存數字,而是:
++counter;
看起來是一句代碼,實際通常可以拆成:
读取 counter
↓
加 1
↓
写回 counter
兩個線程交錯執行時,就可能出現:
线程 A 读到 10
线程 B 读到 10
线程 A 写回 11
线程 B 也写回 11
明明執行了兩次自增,結果只增加了 1。
更重要的是,在 C++ 裏這不只是“結果偶爾不準”,而是 數據競爭,屬於未定義行為。
對於這種“單個獨立計數器”的情況,可以使用:
std::atomic<int> counter{0};
1.2.最基礎的 load() 和 store()
原子對象可以顯式讀取和寫入:
std::atomic<int> value{0};
value.store(10);
int x = value.load();
很多簡單場景也允許寫成類似普通變量的形式:
value = 10;
int x = value;
但在學習原子語義時,顯式寫 load() / store() 更容易看出這裏發生的是原子訪問。
可以把它們理解成:
load() :从 atomic 对象中原子地读出一个值
store() :向 atomic 对象中原子地写入一个值
這裏“原子”只針對這一次讀或寫。
例如:
int a = value.load();
int b = other.load();
這兩次 load() 各自是原子的,但它們並不會自動組成一個“同時讀取兩個值”的整體事務。
想要同時讀取倆,還是用mutex方便點。
1.3.原子自增與 fetch_add()
自增、自減這類操作叫 讀-改-寫(read-modify-write, RMW)。
它們不是簡單讀,也不是簡單寫,而是:
读出旧值
↓
计算新值
↓
写回新值
atomic 的關鍵價值之一,就是能把這整個過程做成一個不可被其他線程插進來的原子操作。
#include <atomic>
#include <print>
#include <thread>
#include <vector>
std::atomic<int> counter{0};
void add_many()
{
for (int i = 0; i < 100000; ++i)
{
++counter;
}
}
int main()
{
std::vector<std::thread> threads;
for (int i = 0; i < 4; ++i)
{
threads.emplace_back(add_many);
}
for (auto& thread : threads)
{
thread.join();
}
std::println("{}", counter.load());
}
運行結果:
400000
還可以顯式使用:
counter.fetch_add(1);
常見原子讀改寫操作包括:
fetch_add()
fetch_sub()
fetch_and()
fetch_or()
fetch_xor()
其中位運算版本主要用於整數原子類型。
fetch_add() 和 ++counter 的區別是:
++counter;
更接近“把值加一,並得到加完之後的新值”;
counter.fetch_add(1);
更明確表達“原子地加 1”,並返回加之前的舊值。
例如:
int old = counter.fetch_add(1);
如果 old == 10,説明這次操作把 counter 從 10 改成了 11。
1.4.exchange()
exchange() 會原子地:
- 寫入新值;
- 返回舊值。
std::atomic<int> value{10};
int old = value.exchange(20);
執行後:
old == 10
value == 20
這種“替換並取回舊值”的操作在狀態切換裏很常見。
例如一個任務狀態從:
idle
切換成:
running
同時還想知道切換前到底是什麼狀態,就可以使用 exchange()。
它比:
int old = value.load();
value.store(20);
更強,因為後者中間可能被其他線程插入修改。
1.5.std::atomic<bool>:狀態標記
#include <atomic>
#include <chrono>
#include <print>
#include <thread>
std::atomic<bool> running{true};
void worker()
{
while (running.load())
{
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
std::println("stopped");
}
int main()
{
std::thread t(worker);
std::this_thread::sleep_for(std::chrono::milliseconds(350));
running.store(false);
t.join();
}
運行結果:
stopped
這種簡單停止標記很適合原子變量。
這裏的 running 只表達一個獨立事實:
线程是否继续循环
它不需要和其他多個字段一起保持複雜不變量,所以用 atomic<bool> 很合適。
如果停止時還要同時修改很多共享狀態,例如:
running = false
queue.clear()
error_code = xxx
那就不能只因為其中一個字段是 atomic,就認為整個停止過程自動安全。此時通常要重新考慮 mutex 或更完整的停止協議。
mutex可以製造一大段的臨界區,而atomic不能,所以這種要好幾行臨界的就用mutex。
但如果使用 C++20,還應該瞭解後面的 std::jthread 和 std::stop_token,因為它們提供了更結構化的協作式停止機制。
1.6.atomic 不等於“整個對象線程安全”
如果一個類有多個字段:
std::atomic<int> x;
std::atomic<int> y;
只能保證對 x 和 y 各自的原子操作不會撕裂。
它並不會自動保證:
x 和 y 永远表示同一个逻辑状态
例如:
x.store(10);
y.store(20);
另一個線程完全可能在兩次寫入之間觀察到:
x == 10
y == 旧值
如果多個變量必須作為整體保持一致,通常應該使用同一把 mutex 保護整個狀態。
可以用一句話記:
atomic 保護的是“一個對象的一次操作”,mutex 更適合保護“一組狀態的不變量”。
例如銀行賬户裏同時有:
balance
last_update_time
version
如果這三個值必須一起變化,那麼把它們分別做成 atomic 並不一定讓設計更清楚。
2.CAS 與原子標誌
2.1.compare_exchange:比較並交換(CAS)
CAS 是很多無鎖算法的核心操作。
不過初學時不要先把它想得太神秘。
CAS 最核心的語義就是:
只有當值還是我以為的那個舊值時,才把它改成新值。
概念上它做的是:
如果 atomic 当前值 == expected
atomic = desired
返回成功
否则
expected = atomic 当前值
返回失败
C++ 提供:
compare_exchange_weak()
compare_exchange_strong()
示例:
#include <atomic>
#include <print>
int main()
{
std::atomic<int> value{10};
int expected = 10;
bool success = value.compare_exchange_strong(expected, 20);
std::println("{}", success);
std::println("{}", value.load());
}
運行結果:
true
20
如果 expected 不匹配:
std::atomic<int> value{10};
int expected = 5;
bool success = value.compare_exchange_strong(expected, 20);
那麼:
success == false
value 仍然是 10
expected 被更新成 10
這裏 expected 會被改寫,是 CAS 很重要的細節。
它不是單純的“輸入參數”,失敗時它會告訴你:
你猜的旧值不对,当前真实值是这个
所以 CAS 循環通常會寫成:
先读当前值作为 expected
↓
根据 expected 算 desired
↓
尝试 compare_exchange
↓
失败时 expected 已被更新,重新计算 desired
2.1.1.weak 和 strong
compare_exchange_weak() 允許“偽失敗”:即使值相等,也可能返回失敗。
因此它很適合放在循環中:
int expected = value.load();
while (!value.compare_exchange_weak(expected, expected + 1))
{
}
這段代碼能工作,是因為失敗時 expected 會被更新成 atomic 當前值。
下一輪循環中的:
expected + 1
就會基於更新後的值繼續嘗試。
compare_exchange_strong() 不允許這種偽失敗,更適合只嘗試一次的場景。
簡單記法:
循环里反复尝试
→ weak 常见
只想尝试一次
→ strong 更直观
2.2.std::atomic_flag
std::atomic_flag 可以理解成一個非常簡單的原子開關。
它只有兩種狀態:
false:空闲
true :已占用
它常見的兩個操作是:
test_and_set()
clear()
其中最關鍵的是:
flag.test_and_set();
這個操作會原子地完成兩件事:
- 返回
flag原來的值; - 同時把
flag設置為true。
例如:
std::atomic_flag flag = ATOMIC_FLAG_INIT;
初始時可以理解為:
flag = false
第一次執行:
bool old = flag.test_and_set();
結果相當於:
old = false
flag = true
如果這時候另一個線程再次執行:
bool old = flag.test_and_set();
那麼結果就是:
old = true
flag = true
因此,可以利用它實現一個最簡單的自旋鎖:
std::atomic_flag flag = ATOMIC_FLAG_INIT;
while (flag.test_and_set())
{
// 自旋等待
}
// 临界区
flag.clear();
這段代碼的邏輯是:
flag == false
↓
线程执行 test_and_set()
↓
返回 false,同时把 flag 改为 true
↓
while(false)
↓
成功进入临界区
如果另一個線程這時候也來執行:
flag.test_and_set();
由於 flag 已經是 true,所以它會返回 true:
while (true)
{
}
於是這個線程會不斷重複執行:
flag.test_and_set();
flag.test_and_set();
flag.test_and_set();
...
直到持有鎖的線程執行:
flag.clear();
把:
flag = true
重新變成:
flag = false
等待中的線程下一次執行 test_and_set() 時,就可以成功獲得這個“鎖”。
這種不斷循環檢查鎖有沒有釋放的行為,就叫做:
自旋(spin)
可以把它理解成:
一個線程發現鎖被別人佔用了之後,不睡眠,也不阻塞,而是一直站在那裏反覆詢問:“鎖釋放了嗎?”
所以自旋鎖和普通 std::mutex 的區別之一就在這裏。
普通互斥量在等待時間較長時,線程通常可以被操作系統阻塞或掛起,避免持續佔用 CPU。
而自旋鎖:
while (flag.test_and_set())
{
}
會一直執行循環,因此可能持續佔用 CPU。
例如:
while (flag.test_and_set())
{
}
// 临界区
read_file();
send_network();
sleep();
do_heavy_work();
flag.clear();
如果臨界區執行時間很長,其他線程就會一直在外面自旋等待,白白浪費 CPU。
因此,自旋鎖更適合臨界區非常短的情況,例如:
while (flag.test_and_set())
{
}
++value;
flag.clear();
如果鎖很快就會被釋放,那麼線程稍微自旋幾次,可能比讓操作系統把線程掛起、再重新喚醒的開銷更低。
但在普通業務代碼中,一般不要因為自旋鎖“看起來簡單”就自己使用 std::atomic_flag 實現鎖。
如果臨界區中存在:
IO
sleep
网络请求
文件操作
复杂计算
可能阻塞的函数
通常更適合使用:
std::mutex
配合:
std::lock_guard
std::scoped_lock
std::unique_lock
來進行同步。
std::atomic_flag 真正重要的地方在於:
test_and_set()
中的:
读取旧值 + 设置为 true
是一個不可分割的原子操作。
如果使用普通 bool:
bool flag = false;
兩個線程可能同時發生:
线程 A:看到 flag == false
线程 B:也看到 flag == false
线程 A:flag = true
线程 B:flag = true
线程 A:认为自己获得了锁
线程 B:也认为自己获得了锁
這樣兩個線程就可能同時進入臨界區。
而 std::atomic_flag::test_and_set() 可以保證多個線程同時競爭時,只有一個線程能夠看到原來的值是 false,從而成功進入臨界區。
因此,可以把 std::atomic_flag 簡單理解為:
std::atomic_flag是一個原子的“佔用標誌”。test_and_set()會原子地讀取舊值並把標誌設置為true,因此可以用它實現最簡單的自旋鎖;沒有搶到鎖的線程會不斷循環檢查,這種行為就叫自旋。
2.3.volatile 不能替代 atomic
這是非常常見的誤區。
volatile bool running;
不能用來解決 C++ 多線程同步問題。
volatile 主要表達的是:
這個對象的訪問具有特殊可觀察意義,編譯器不能像普通對象那樣隨意消除相關訪問。
它不提供:
- 原子性;
- 跨線程同步;
- happens-before 關係;
- 數據競爭保護。
線程間共享狀態應使用 std::atomic、mutex 或其他標準同步工具。
可以粗略記成:
volatile:主要解决“编译器不要省略这次访问”
atomic :解决“多线程下这次访问如何正确同步”
它們不是同一類工具。
3.內存序
3.1.為什麼 atomic 還有第二個參數
你會看到:
value.store(10, std::memory_order_release);
int x = value.load(std::memory_order_acquire);
這裏的第二個參數控制的是:
內存順序(memory ordering)
學習內存序時,最重要的是先區分兩個概念:
原子性
和
内存访问之间的顺序
原子性解決的是:
這個 atomic 對象本身的操作是不是不可分割的?
例如:
counter.fetch_add(1);
多個線程同時執行時,不會因為普通的“讀取 → 加一 → 寫回”互相覆蓋而把計數加丟。
而內存序解決的是:
這個 atomic 操作前後的其他內存讀寫,應該和其他線程建立什麼樣的順序關係?
例如:
int data = 0;
std::atomic<bool> ready{false};
線程 A:
data = 42;
ready.store(true, ...);
線程 B:
while (!ready.load(...))
{
}
std::println("{}", data);
這裏我們不只關心:
ready 能不能正确读到 true
還關心:
线程 B 看到 ready == true 以后,
能不能保证看到线程 A 之前写好的 data = 42。
這就是內存序要解決的問題。
C++ 常見的內存序包括:
std::memory_order_relaxed
std::memory_order_acquire
std::memory_order_release
std::memory_order_acq_rel
std::memory_order_seq_cst
還有:
std::memory_order_consume
但實際工程中通常不要主動使用 consume。
3.2.默認內存序:seq_cst
如果不寫第二個參數:
value.store(10);
int x = value.load();
默認使用:
std::memory_order_seq_cst
它是最強、也最容易理解的常用內存序。
可以先粗略理解成:
所有線程看到這些原子操作時,效果儘量像它們都處在一個大家一致認可的全局順序中。
所以初學階段:
不知道应该用什么
→ 直接使用默认的 seq_cst
通常是最穩妥的選擇。
不要為了所謂“性能優化”,看到:
std::memory_order_relaxed
就把所有原子操作都改成 relaxed。
內存序寫錯以後,程序可能平時運行完全正常,但換個平台、編譯器或優化級別以後才出現非常難排查的併發問題。
3.3.memory_order_relaxed
relaxed 只要求:
這個 atomic 對象本身的操作仍然是原子的。
例如:
counter.fetch_add(
1,
std::memory_order_relaxed);
多個線程同時執行時,計數仍然不會因為併發更新而丟失。
所以像這種:
独立统计次数
请求计数
事件发生次数
通常可以考慮 relaxed。
因為我們只關心:
counter 自己最后是多少
並不需要通過這個 counter 去同步其他普通變量。
但要注意:
relaxed只保證 atomic 自己的訪問,不會順便讓旁邊的普通變量變得線程安全。
例如:
int data = 0;
std::atomic<bool> ready{false};
void producer()
{
data = 42;
ready.store(
true,
std::memory_order_relaxed);
}
另一個線程即使:
ready.load(std::memory_order_relaxed)
讀到了 true,也不能單純依靠這個 relaxed 操作建立:
data = 42
和
读取 data
之間所需要的跨線程同步關係。
3.4.acquire / release
acquire / release 最經典的用途是:
一個線程先準備數據,然後通過 atomic 發佈“數據已經準備好了”;另一個線程看到這個信號以後再讀取數據。
例如:
#include <atomic>
#include <print>
#include <thread>
int data = 0;
std::atomic<bool> ready{false};
void producer()
{
data = 42;
ready.store(
true,
std::memory_order_release);
}
void consumer()
{
while (!ready.load(
std::memory_order_acquire))
{
}
std::println("{}", data);
}
int main()
{
std::thread t1(producer);
std::thread t2(consumer);
t1.join();
t2.join();
}
運行結果:
42
可以這樣理解:
producer:
data = 42
↓
先把普通数据准备好
↓
release store
↓
发布 ready = true
consumer:
acquire load
↓
看到 ready == true
↓
接收到 producer 发布的数据
↓
读取 data
也就是:
写普通数据
↓
release
↓
acquire
↓
读普通数据
這裏 ready 本身只是一個同步信號。
真正重要的是:
producer 在
release之前完成的操作,可以通過對應的acquire與 consumer 建立同步關係。
所以可以簡單記成:
release
→ 发布之前完成的工作
acquire
→ 接收对方发布的工作
3.5.memory_order_acq_rel
有些原子操作既會讀取舊值,又會寫入新值,例如:
fetch_add()
exchange()
compare_exchange()
這種操作有時同時需要:
acquire
+
release
於是可以使用:
std::memory_order_acq_rel
可以先簡單理解成:
既接收别人之前发布的数据
又把自己之前完成的操作继续发布出去
這一部分更多出現在複雜的無鎖數據結構中,初學階段知道它的含義即可。
最重要的是記住:
原子性
→ atomic 自己的操作是否不可分割
内存序
→ atomic 操作和其他内存访问之间建立怎样的跨线程顺序
以及一個實用原則:
默認先用
seq_cst;只有在明確理解同步關係,並且確實有必要時,再主動使用relaxed、acquire、release等更弱的內存序。
4.等待與實現特性
4.1.is_lock_free()
std::atomic<T> 保證的是:
對這個對象的原子操作具有標準規定的線程安全語義。
但它不保證底層一定完全不用鎖。
例如:
std::atomic<int> value{0};
bool lock_free = value.is_lock_free();
is_lock_free() 用來查詢:
當前這個原子對象,在當前平台和當前實現中,底層操作是否可以不依賴額外的鎖來完成。
如果返回:
true
説明它的原子操作是 lock-free 的,也就是底層通常可以直接依靠 CPU 提供的原子指令完成。
如果返回:
false
説明標準庫為了實現這個原子操作,內部可能需要藉助某種鎖或其他同步機制。
例如:
std::atomic<int> value{0};
std::println("{}", value.is_lock_free());
在常見的 x86-64、ARM 等平台上,像:
std::atomic<int>
std::atomic<bool>
std::atomic<int*>
這種比較小的類型通常都是 lock-free 的。
但是對於比較大的類型,情況就不一定了。
例如:
struct Data
{
long long a;
long long b;
};
std::atomic<Data> data;
這種對象比較大,CPU 可能沒有辦法用一條或少量原子指令直接完成操作。
此時標準庫仍然可以提供:
data.load();
data.store(...);
這樣的原子語義,但內部可能需要通過鎖來實現。
因此:
std::atomic<T>
應該理解成:
保證“操作是原子的”。
而不是:
保證“實現一定無鎖”。
還可以使用:
std::atomic<int>::is_always_lock_free
它是一個編譯期常量,用來判斷:
這個
std::atomic<int>類型在當前實現中是否永遠都是 lock-free 的。
例如:
if constexpr (std::atomic<int>::is_always_lock_free)
{
// 当前实现保证 atomic<int> 永远是 lock-free
}
可以簡單區分成:
value.is_lock_free()
表示:
這個具體 atomic 對象現在是不是 lock-free。
而:
std::atomic<T>::is_always_lock_free
表示:
這個 atomic 類型在當前實現中是不是始終保證 lock-free。
所以最重要的一點是:
atomic ≠ 一定无锁
atomic 真正表示的是:
原子操作语义
也就是多個線程同時訪問時,這些操作不會被拆成一半讓其他線程插進來。
至於底層到底是:
CPU 原子指令
還是:
标准库内部借助锁实现
這是具體平台和標準庫實現決定的。
因此可以記成一句話:
std::atomic<T>保證的是“原子性”,不保證“一定無鎖”;is_lock_free()就是用來查詢它底層是否真正採用無鎖實現的。
4.2.C++20:wait() / notify_one() / notify_all()
C++20 給原子類型增加了:
wait()
notify_one()
notify_all()
它們可以讓線程等待某個原子變量的值發生變化,而不需要自己一直寫循環忙等。
例如:
#include <atomic>
#include <print>
#include <thread>
std::atomic<bool> ready{false};
void worker()
{
ready.wait(false);
std::println("start");
}
int main()
{
std::thread t(worker);
ready.store(true);
ready.notify_one();
t.join();
}
運行結果:
start
這裏最關鍵的是:
ready.wait(false);
它的意思不是:
等待 ready 变成 false
而是:
如果
ready當前仍然等於false,就等待;直到它不再等於false才返回。
所以執行過程可以理解成:
初始:
ready = false
worker 執行:
ready.wait(false);
發現:
ready == false
也就是當前值和傳進去的舊值 false 相同,於是開始等待。
隨後 main 執行:
ready.store(true);
把:
ready = false
改成:
ready = true
然後:
ready.notify_one();
通知一個正在等待 ready 的線程:
這個值可能發生變化了,你可以重新檢查一下。
worker 被喚醒後發現:
ready != false
於是:
ready.wait(false);
返回,繼續向下執行:
std::println("start");
所以整個過程可以畫成:
worker main
| |
| ready.wait(false) |
| |
| ready == false |
| 开始等待 |
| |
| ready.store(true)
| |
| ready.notify_one()
| |
| <------ 被唤醒 ------------|
|
| ready != false
|
| wait() 返回
|
| println("start")
如果不用 wait(),以前可能會寫成:
while (!ready.load())
{
}
這個循環會不斷執行:
ready.load();
ready.load();
ready.load();
ready.load();
...
也就是線程一直佔着 CPU 檢查:
變成
true了嗎?
這種方式叫做忙等 / 自旋等待。
而:
ready.wait(false);
在需要等待較長時間時,可以讓線程阻塞等待,不需要一直空轉消耗 CPU。
因此:
while (!ready.load())
{
}
可以理解成:
我一直盯着門看它有沒有開。
而:
ready.wait(false);
更像:
門沒開我就先睡覺,等有人叫我再起來看看。
4.3.notify_one() 和 notify_all()
如果只有一個等待線程,可以:
ready.notify_one();
表示:
喚醒一個等待這個原子變量的線程。
如果有多個線程:
void worker()
{
ready.wait(false);
std::println("start");
}
然後創建:
std::thread t1(worker);
std::thread t2(worker);
std::thread t3(worker);
如果希望全部線程都被喚醒,可以:
ready.store(true);
ready.notify_all();
也就是:
notify_one() → 通知一个等待线程
notify_all() → 通知所有等待线程
4.4.wait() 到底在等什麼?
記住這個規則就夠了:
atomic.wait(old_value);
意思是:
只要當前值仍然等於
old_value,就繼續等待;一旦當前值不等於old_value,就返回。
例如:
std::atomic<int> value{10};
value.wait(10);
表示:
value == 10
↓
继续等待
如果其他線程執行:
value.store(20);
value.notify_one();
那麼:
value != 10
於是 wait(10) 返回。
所以:
wait(x)
不要理解成:
等到值變成
x
而應該理解成:
值還是
x的時候就等,等它不是x了再繼續。
4.5.和 condition_variable 有什麼區別? (學完condition_variable再回來看)
如果你的需求非常簡單:
ready 从 false 变成 true
state 从 0 变成其他值
counter 不再等于某个旧值
直接:
atomic.wait()
atomic.notify_one()
atomic.notify_all()
會非常方便。
但是如果你的等待條件比較複雜,例如:
队列非空
或者 finished == true
或者 error != 0
這種條件涉及多個變量和共享數據:
queue.empty()
finished
error
通常還是使用:
std::condition_variable
+
std::mutex
+
谓词
更加清楚。
因此可以簡單記成:
atomic::wait()適合等待“一個原子變量的值發生變化”;condition_variable更適合等待“複雜條件成立”。
最後把 wait() 的核心記住就行:
value.wait(x);
等價於一句人話:
“只要 value 還是 x,我就繼續等;什麼時候不是 x 了,我什麼時候繼續執行。”
5.選擇與常見錯誤
5.1.atomic 和 mutex 怎麼選
| 場景 | 推薦 |
|---|---|
| 簡單計數器 | std::atomic |
| 簡單布爾狀態 | std::atomic<bool> |
| 一個指針或整數的獨立狀態切換 | std::atomic |
| 多個字段必須一起保持一致 | std::mutex |
修改 vector、map 等複雜容器 |
通常用 std::mutex |
| 複雜業務邏輯臨界區 | std::mutex |
| 等待複雜條件 | std::condition_variable + mutex |
不要為了追求“無鎖”而強行把一個本來適合 mutex 的複合狀態拆成很多 atomic。
代碼正確性和可維護性通常比理論上的鎖開銷更重要。
一個實用判斷是:
能用一句话说清楚“这个 atomic 独立表示什么状态”
→ 可以考虑 atomic
需要解释好几个字段之间必须如何配合
→ 通常先考虑 mutex
5.2.常見錯誤
- 把
std::atomic理解成“一定 lock-free”。 - 把
volatile當作線程同步工具。 - 用多個 atomic 表達一個必須整體一致的狀態,卻沒有額外同步。
- 在不理解內存模型時隨意把所有操作改成
memory_order_relaxed。 - 自己實現自旋鎖並在臨界區長時間工作,導致 CPU 空轉。
- 認為“atomic 變量安全”就等於“包含它的整個類都線程安全”。
6.小結
std::atomic<T>保證針對該對象的原子操作語義,但不保證所有實現一定 lock-free。load()/store()負責原子讀寫;fetch_add()等負責原子讀改寫。exchange()可以原子替換並獲得舊值。compare_exchange_weak/strong()是 CAS 操作,也是很多無鎖算法的基礎。volatile不能替代atomic。- 默認內存序是
memory_order_seq_cst,初學階段優先使用默認值。 relaxed、acquire、release屬於更深入的內存模型內容,必須在理解同步關係後再使用。- C++20 的
atomic::wait/notify可以避免簡單狀態等待時的忙輪詢。