跳到正文

Wiki

std::atomic

約 15 分鐘閱讀

本文由簡體中文內容確定性轉換,並受版本化術語表保護。

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() 會原子地:

  1. 寫入新值;
  2. 返回舊值。
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::jthreadstd::stop_token,因為它們提供了更結構化的協作式停止機制。

1.6.atomic 不等於“整個對象線程安全”

如果一個類有多個字段:

std::atomic<int> x;
std::atomic<int> y;

只能保證對 xy 各自的原子操作不會撕裂。

它並不會自動保證:

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.weakstrong

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();

這個操作會原子地完成兩件事

  1. 返回 flag 原來的值;
  2. 同時把 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::atomicmutex 或其他標準同步工具。

可以粗略記成:

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;只有在明確理解同步關係,並且確實有必要時,再主動使用 relaxedacquirerelease 等更弱的內存序。

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
修改 vectormap 等複雜容器 通常用 std::mutex
複雜業務邏輯臨界區 std::mutex
等待複雜條件 std::condition_variable + mutex

不要為了追求“無鎖”而強行把一個本來適合 mutex 的複合狀態拆成很多 atomic。

代碼正確性和可維護性通常比理論上的鎖開銷更重要。

一個實用判斷是:

能用一句话说清楚“这个 atomic 独立表示什么状态”
→ 可以考虑 atomic

需要解释好几个字段之间必须如何配合
→ 通常先考虑 mutex

5.2.常見錯誤

  1. std::atomic 理解成“一定 lock-free”。
  2. volatile 當作線程同步工具。
  3. 用多個 atomic 表達一個必須整體一致的狀態,卻沒有額外同步。
  4. 在不理解內存模型時隨意把所有操作改成 memory_order_relaxed
  5. 自己實現自旋鎖並在臨界區長時間工作,導致 CPU 空轉。
  6. 認為“atomic 變量安全”就等於“包含它的整個類都線程安全”。

6.小結

  • std::atomic<T> 保證針對該對象的原子操作語義,但不保證所有實現一定 lock-free。
  • load() / store() 負責原子讀寫;fetch_add() 等負責原子讀改寫。
  • exchange() 可以原子替換並獲得舊值。
  • compare_exchange_weak/strong() 是 CAS 操作,也是很多無鎖算法的基礎。
  • volatile 不能替代 atomic
  • 默認內存序是 memory_order_seq_cst,初學階段優先使用默認值。
  • relaxedacquirerelease 屬於更深入的內存模型內容,必須在理解同步關係後再使用。
  • C++20 的 atomic::wait/notify 可以避免簡單狀態等待時的忙輪詢。