C++
std::atomic
发布于 2026年7月22日
std::atomic
std::atomic 是 C++ 标准库提供的原子类型,用于多线程环境下安全地读写共享数据。
它的核心保证是:对原子对象的一次操作不会被其他线程“做到一半时插入”。但要注意:
原子变量只能保证针对该原子对象的操作安全,并不会自动让一整段代码都线程安全。
需要包含:
#include <atomic>
1. 基本用法
#include <atomic>
#include <iostream>
#include <thread>
#include <vector>
std::atomic<int> counter{0};
void work()
{
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(work);
}
for (auto& t : threads) {
t.join();
}
std::cout << counter.load() << '\n'; // 400000
}
如果使用普通 int:
int counter = 0;
多个线程执行 ++counter 会产生数据竞争,程序行为未定义。因为 ++counter 实际包含读取、加一和写回三个步骤。
2. 初始化
推荐直接初始化:
std::atomic<int> count{0};
std::atomic<bool> ready{false};
std::atomic<int*> ptr{nullptr};
原子对象不能像普通变量一样复制:
std::atomic<int> a{10};
// std::atomic<int> b = a; // 错误
// b = a; // 错误
应该显式读取和写入:
std::atomic<int> a{10};
std::atomic<int> b{a.load()};
b.store(a.load());
3. load() 和 store()
load():原子读取
std::atomic<int> value{10};
int x = value.load();
store():原子写入
value.store(20);
很多时候也能使用类似普通变量的语法:
value = 30;
int x = value;
但显式写成 load()、store() 通常更清楚,也方便指定内存序。
4. exchange():交换并返回旧值
std::atomic<int> value{10};
int old = value.exchange(20);
std::cout << old << '\n'; // 10
std::cout << value.load() << '\n'; // 20
它相当于原子地执行:
old = value;
value = 20;
常见用途是实现简单的状态切换:
std::atomic<bool> busy{false};
if (!busy.exchange(true)) {
// 当前线程成功将 false 改为 true
// 执行独占任务
busy.store(false);
}
不过真正需要保护临界区时,通常优先使用 std::mutex,不要随意手写锁。
5. 原子加减操作
对于整数原子类型,可以使用:
std::atomic<int> count{0};
count.fetch_add(3); // 加 3
count.fetch_sub(2); // 减 2
++count;
count++;
--count;
count--;
fetch_add() 返回修改之前的值:
std::atomic<int> count{10};
int old = count.fetch_add(5);
std::cout << old << '\n'; // 10
std::cout << count.load() << '\n'; // 15
前置和后置运算符的返回规则与普通整数类似:
std::atomic<int> count{10};
int a = ++count; // a == 11
int b = count++; // b == 11,之后 count == 12
整数原子还支持:
value.fetch_and(mask);
value.fetch_or(mask);
value.fetch_xor(mask);
例如设置状态位:
constexpr unsigned readable = 1u << 0;
constexpr unsigned writable = 1u << 1;
std::atomic<unsigned> flags{0};
flags.fetch_or(readable); // 设置 readable
flags.fetch_or(writable); // 设置 writable
flags.fetch_and(~readable); // 清除 readable
6. compare_exchange:比较并交换
这是无锁算法中最重要的操作之一。
其逻辑类似:
if (atomic_value == expected) {
atomic_value = desired;
return true;
} else {
expected = atomic_value;
return false;
}
实际代码:
std::atomic<int> value{10};
int expected = 10;
bool success = value.compare_exchange_strong(expected, 20);
成功时:
success == true
value == 20
expected == 10
失败时,expected 会被更新为原子变量当前的值:
std::atomic<int> value{15};
int expected = 10;
bool success = value.compare_exchange_strong(expected, 20);
// success == false
// value == 15
// expected == 15
循环更新示例
假设要原子地更新最大值:
void update_max(std::atomic<int>& maximum, int candidate)
{
int current = maximum.load();
while (current < candidate &&
!maximum.compare_exchange_weak(current, candidate)) {
// 失败后,current 已自动更新为 maximum 的最新值
}
}
也可以写成:
std::atomic<int> maximum{0};
void update_max(int candidate)
{
int current = maximum.load();
while (candidate > current) {
if (maximum.compare_exchange_weak(current, candidate)) {
break;
}
}
}
C++26 为部分原子类型增加了 fetch_max() 和 fetch_min();如果项目使用更早标准,则需要上述 CAS 循环。
7. weak 和 strong 的区别
提供两个比较交换函数:
compare_exchange_weak()
compare_exchange_strong()
compare_exchange_weak
允许“伪失败”:即使当前值等于 expected,也可能返回 false。
适合放在循环中:
while (!value.compare_exchange_weak(expected, desired)) {
// 重试
}
compare_exchange_strong
不会因为伪失败而失败,适合只尝试一次的场景:
if (value.compare_exchange_strong(expected, desired)) {
// 修改成功
}
简单记忆:
- 循环重试:通常用
weak - 只判断一次:通常用
strong
8. 常见原子类型
基本形式
std::atomic<bool>
std::atomic<int>
std::atomic<unsigned>
std::atomic<long>
std::atomic<MyType>
标准库还提供了一些别名:
std::atomic_bool
std::atomic_int
std::atomic_uint
std::atomic_long
std::atomic_size_t
例如:
std::atomic_int count{0};
std::atomic_bool stopped{false};
自定义类型
某些简单类型可以作为 std::atomic<T> 的参数:
struct Point {
int x;
int y;
};
static_assert(std::is_trivially_copyable_v<Point>);
std::atomic<Point> point{{1, 2}};
一般要求 T 是可平凡复制类型。即使允许构造,也不代表它一定使用硬件无锁指令。
9. is_lock_free()
可以检查当前原子类型在当前平台上是否无锁:
std::atomic<int> value{0};
std::cout << std::boolalpha
<< value.is_lock_free()
<< '\n';
编译期属性:
std::atomic<int>::is_always_lock_free
例如:
static_assert(std::atomic<int>::is_always_lock_free);
不过这条断言并不一定在所有平台都成立。
重要区别:
- 原子操作:语义上不可分割、线程安全
- 无锁实现:底层实现没有使用锁
std::atomic<T> 保证原子性,但不保证一定是无锁实现。
10. 内存序 memory_order
原子操作不仅涉及“操作是否被打断”,还涉及:
- 编译器能否调整指令顺序
- CPU 能否调整内存访问顺序
- 一个线程何时能看到另一个线程的普通数据修改
默认内存序是:
std::memory_order_seq_cst
它最容易理解,也最严格。
常用内存序包括:
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,一般按 acquire 处理。
11. memory_order_relaxed
只保证原子对象本身的操作不可分割,不负责建立线程之间的同步关系。
std::atomic<int> counter{0};
counter.fetch_add(1, std::memory_order_relaxed);
适用于只关心计数结果、不依赖其他共享数据可见性的场景,例如统计请求次数:
std::atomic<unsigned long long> requests{0};
void handle_request()
{
requests.fetch_add(1, std::memory_order_relaxed);
// 处理请求
}
relaxed 仍然是原子的,不会丢失更新;只是它不会使其他普通变量的读写自动同步。
12. release 和 acquire
这是最常见的线程间发布模式。
#include <atomic>
#include <iostream>
#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::cout << data << '\n'; // 安全地读到 42
}
发生关系是:
生产者写 data
↓
release 写 ready
↓ 同步
acquire 读 ready
↓
消费者读 data
只要消费者的 acquire 读取到了生产者 release 写入的值,生产者在 release 之前的修改就对消费者可见。
注意:data 虽然不是原子变量,但这里不会发生数据竞争,因为 release/acquire 建立了正确的同步关系。
如果改成:
ready.store(true, std::memory_order_relaxed);
和:
ready.load(std::memory_order_relaxed);
就不能用 ready 来保证 data 的安全发布。
13. memory_order_acq_rel
同时具有 acquire 和 release 语义,通常用于“读取并修改”操作:
value.fetch_add(1, std::memory_order_acq_rel);
value.exchange(10, std::memory_order_acq_rel);
含义是:
- acquire:后面的操作不能越过当前操作提前
- release:前面的操作不能越过当前操作推后
纯读取操作不能使用 release,纯写入操作不能使用 acquire:
value.load(std::memory_order_acquire);
value.store(10, std::memory_order_release);
14. memory_order_seq_cst
这是默认选项:
value.store(10);
int x = value.load();
等价于:
value.store(10, std::memory_order_seq_cst);
int x = value.load(std::memory_order_seq_cst);
它除了拥有 acquire/release 的同步效果,还要求所有 seq_cst 原子操作表现得像存在一个全局一致顺序。
初学时建议:
先使用默认 seq_cst,只有经过性能测量和严格推理后,再使用更弱的内存序。
错误的内存序往往非常隐蔽,测试通过也不代表代码正确。
15. CAS 的两个内存序
比较交换可以指定成功与失败时不同的内存序:
value.compare_exchange_weak(
expected,
desired,
std::memory_order_acq_rel, // 成功
std::memory_order_acquire // 失败
);
失败时没有执行写入,因此失败内存序不能是:
std::memory_order_release
std::memory_order_acq_rel
通常先使用默认内存序。如果确实需要优化,再仔细指定。
16. wait() 和 notify_*():C++20
C++20 支持等待原子变量发生变化,避免持续忙循环。
忙等待写法
while (!ready.load()) {
// 不停消耗 CPU
}
使用 wait
#include <atomic>
#include <iostream>
#include <thread>
std::atomic<bool> ready{false};
int data = 0;
void consumer()
{
ready.wait(false);
std::cout << data << '\n';
}
int main()
{
std::thread t(consumer);
data = 42;
ready.store(true);
ready.notify_one();
t.join();
}
ready.wait(false) 的含义是:
当 ready 仍然等于 false 时等待;发现它不等于 false 后返回。
通知方式:
ready.notify_one(); // 唤醒一个等待线程
ready.notify_all(); // 唤醒所有等待线程
常见模式:
state.store(newState, std::memory_order_release);
state.notify_all();
等待方:
state.wait(oldState, std::memory_order_acquire);
wait() 应关注的是“值是否改变”。即使发生通知,只要值仍然与参数相等,它也可以继续等待。
17. std::atomic_flag
std::atomic_flag 是最简单的原子标志类型。
std::atomic_flag flag = ATOMIC_FLAG_INIT;
C++20 也可以直接初始化:
std::atomic_flag flag{};
主要操作:
flag.test_and_set();
flag.clear();
简单自旋锁:
class SpinLock {
public:
void lock()
{
while (flag_.test_and_set(std::memory_order_acquire)) {
}
}
void unlock()
{
flag_.clear(std::memory_order_release);
}
private:
std::atomic_flag flag_ = ATOMIC_FLAG_INIT;
};
使用方式:
SpinLock lock;
void work()
{
lock.lock();
// 临界区
lock.unlock();
}
实际项目中通常优先使用:
std::mutex
std::lock_guard
自旋锁只适合临界区极短、线程不会长时间被挂起且经过性能分析的特殊场景。否则它会白白消耗 CPU。
18. 原子指针
std::atomic<T*> 支持原子地操作指针:
int values[10]{};
std::atomic<int*> ptr{values};
int* old = ptr.fetch_add(1);
这里的 fetch_add(1) 会移动一个 int 元素,而不是一个字节。
常见操作:
ptr.load();
ptr.store(newPtr);
ptr.exchange(newPtr);
ptr.compare_exchange_weak(expected, desired);
原子指针只保证指针本身的读写安全,并不自动保证指针所指向对象的生命周期和内容安全。
例如:
std::atomic<int*> ptr;
不能自动解决:
- 对象何时销毁
- 其他线程是否还持有旧指针
- 指向对象的字段是否存在数据竞争
19. 原子智能指针
C++20 支持:
std::atomic<std::shared_ptr<T>>
std::atomic<std::weak_ptr<T>>
示例:
#include <atomic>
#include <memory>
struct Config {
int timeout;
};
std::atomic<std::shared_ptr<const Config>> config{
std::make_shared<const Config>(Config{100})
};
void update_config()
{
auto next = std::make_shared<const Config>(Config{200});
config.store(next, std::memory_order_release);
}
void read_config()
{
auto current = config.load(std::memory_order_acquire);
if (current) {
int timeout = current->timeout;
}
}
这种方式适合:
- 配置对象整体替换
- 多线程读取同一份不可变快照
- 自动管理旧对象生命周期
这里推荐把对象设计为不可变,否则智能指针自身虽然原子,所指对象仍可能发生数据竞争。
20. std::atomic_ref:C++20
std::atomic_ref<T> 可以对已有普通对象进行原子访问:
#include <atomic>
int value = 0;
void add()
{
std::atomic_ref<int> ref(value);
ref.fetch_add(1);
}
它不会创建新对象,而是把现有对象视为原子对象操作。
注意事项:
- 被引用对象必须满足对齐要求。
atomic_ref存在期间,不能同时以普通非原子方式访问该对象。- 多个线程应当统一使用原子方式访问它。
错误示例:
int value = 0;
std::atomic_ref<int> ref(value);
ref.fetch_add(1);
value++; // 如果同时有其他线程操作,可能产生数据竞争
可以检查对齐要求:
std::atomic_ref<int>::required_alignment
21. 原子操作不能组合成原子事务
下面每个操作单独都是原子的:
std::atomic<int> balance{100};
if (balance.load() >= 80) {
balance.fetch_sub(80);
}
但整体不是原子的。两个线程可能同时读到 100,然后都扣除 80,最终变成 -60。
可以使用 CAS 循环:
bool withdraw(std::atomic<int>& balance, int amount)
{
int current = balance.load();
while (current >= amount) {
if (balance.compare_exchange_weak(
current,
current - amount)) {
return true;
}
}
return false;
}
如果逻辑涉及多个变量或复杂不变量,通常应该使用互斥锁:
#include <mutex>
std::mutex mutex;
int balance = 100;
bool withdraw(int amount)
{
std::lock_guard<std::mutex> guard(mutex);
if (balance < amount) {
return false;
}
balance -= amount;
return true;
}
22. volatile 不能替代 atomic
错误认识:
volatile int counter = 0;
volatile 不保证:
- 多线程原子性
- 线程间同步
- 内存可见性
- 不发生数据竞争
它主要用于特殊内存访问场景,例如某些硬件寄存器,而不是常规的线程同步。
多线程共享数据应该使用:
std::atomic
std::mutex
23. atomic 与 mutex 如何选择
适合使用 std::atomic:
- 简单计数器
- 独立布尔状态
- 指针或快照整体替换
- 引用计数
- 无锁数据结构中的基础操作
适合使用 std::mutex:
- 多个变量必须保持一致
- 一次操作包含多个步骤
- 需要保护容器或复杂对象
- 临界区逻辑较复杂
- 可读性和正确性比极限性能更重要
例如,不能因为状态字段是原子类型,就认为整个对象安全:
struct Account {
std::atomic<int> state;
int balance;
std::string owner;
};
state 是线程安全的,不代表 balance 和 owner 也安全。
24. 常见错误总结
错误一:先读再写
atomicValue.store(atomicValue.load() + 1);
两个线程可能读到相同旧值,导致更新丢失。应该使用:
atomicValue.fetch_add(1);
错误二:认为原子对象之间整体原子
std::atomic<int> x{0};
std::atomic<int> y{0};
x.store(1);
y.store(1);
只能保证两次写入分别原子,不能保证其他线程把它们当作一个整体观察。
错误三:用 relaxed 发布普通数据
data = 42;
ready.store(true, std::memory_order_relaxed);
如果 ready 是用来通知其他线程读取 data,应该使用 release/acquire。
错误四:原子指针指向普通可变对象
std::atomic<MyObject*> ptr;
它只保证 ptr 安全,不保证 *ptr 安全。
错误五:无依据地使用复杂内存序
较弱的内存序并不一定带来明显性能提升,却会显著增加证明代码正确性的难度。默认从 seq_cst 开始通常更稳妥。
25. 一个实用的停止标志示例
#include <atomic>
#include <chrono>
#include <iostream>
#include <thread>
std::atomic<bool> stopped{false};
void worker()
{
while (!stopped.load(std::memory_order_relaxed)) {
std::cout << "working...\n";
std::this_thread::sleep_for(std::chrono::milliseconds(200));
}
}
int main()
{
std::thread thread(worker);
std::this_thread::sleep_for(std::chrono::seconds(1));
stopped.store(true, std::memory_order_relaxed);
thread.join();
}
这里只有一个独立停止标志,不依赖其他数据的发布,因此 relaxed 通常足够。
最后可以这样记:
load/store:原子读写fetch_add/fetch_sub:原子增减exchange:无条件替换compare_exchange_*:满足条件才替换wait/notify:等待和通知relaxed:只要原子性release/acquire:发布数据与获取数据seq_cst:最直观、默认、最容易保证正确- 多变量复杂逻辑:优先考虑
std::mutex