C++ 标准库 atomic
下面对 C++11 起引入的 <atomic>
头文件及其无锁并发原语做一次系统、深入的梳理,包括原子类型、基本操作、内存序(memory order)、屏障(fence)、锁‑自由(lock‑free)特性,以及实践建议。
一、概述
<atomic>
提供了原子操作类型和函数,使多个线程在不加锁的情况下也能安全地访问和修改共享数据。- 通过硬件或编译器内置的原子指令实现,通常具有比互斥量更低的开销,但对内存可见性和指令重排更敏感。
- 核心概念:原子类型(atomic types)、原子操作(load/store/exchange/compare_exchange/fetch_…)、内存序(memory_order),以及屏障(atomic_thread_fence)。
二、原子类型
1. std::atomic<T>
- 模板化的原子类型,支持对大多数可平凡拷贝(TriviallyCopyable)类型的原子操作。常见用法:
std::atomic<int> ai{0}; std::atomic<bool> flag{false}; std::atomic<MyStruct*> ptr{nullptr};
- 特化:若
T
为整型或指针,通常编译器可生成单条机器指令;如果T
较大,则可能退化为内部锁。
2. 原子别名
- C++20 提供:
using atomic_int = std::atomic<int>; using atomic_bool = std::atomic<bool>; using atomic_flag = std::atomic_flag;
std::atomic_flag
是最轻量的布尔标志,只支持测试与置位。
三、基本原子操作
所有操作都可带可选的 内存序(默认为 std::memory_order_seq_cst
,即最强的全序)。
1. 载入与存储
int v = ai.load(std::memory_order_acquire);
ai.store(v+1, std::memory_order_release);
load
:以给定内存序读取当前值。store
:以给定内存序写入新值。
2. 交换(exchange)
int old = ai.exchange(42, std::memory_order_acq_rel);
- 原子地将新值写入,返回旧值,具备“读-改-写”原子性。
3. 比较并交换(compare_exchange)
int expected = old;
bool ok = ai.compare_exchange_strong(
expected, new_val,
std::memory_order_acq_rel,
std::memory_order_acquire);
if (!ok) {
// expected 被更新为 ai 当前值
}
compare_exchange_strong
:在当前值等于expected
时才写入new_val
,否则将当前值写回expected
,并返回false
。compare_exchange_weak
可容忍“虚假失败”(spurious failure),适合在循环中使用更高效。
4. 原子算术与位操作(fetch_…)
ai.fetch_add(1, std::memory_order_acq_rel); // ++ai
ai.fetch_sub(2, std::memory_order_acq_rel); // ai -= 2
ai.fetch_and(mask, std::memory_order_acq_rel);
ai.fetch_or(bits, std::memory_order_acq_rel);
ai.fetch_xor(bits, std::memory_order_acq_rel);
- 返旧值,或者使用后缀语义。
5. 原子标志:std::atomic_flag
- 测试并置位(test‑and‑set):
if (!flag.test_and_set(std::memory_order_acquire)) { // 首次进入 }
- 清除:
flag.clear(std::memory_order_release);
四、内存序(Memory Order)
C++ 定义了六种内存序,以控制操作与其他原子或普通内存访问的可见性与重排限制:
枚举 | 含义 |
---|---|
memory_order_relaxed | 仅保证原子性,不做同步或排序约束 |
memory_order_consume (C++20 deprecated) | 数据依赖顺序保证 |
memory_order_acquire | 当前线程后续读写不能与此原子读之前重排 |
memory_order_release | 当前线程前序读写不能与此原子写之后重排 |
memory_order_acq_rel | 同时具备 acquire 和 release 特性 |
memory_order_seq_cst | 最强全序一致性,所有线程看见同一全序顺序 |
- 常见用法:生产者
store(..., release)
,消费者load(..., acquire)
。
五、原子线程屏障(Fences)
std::atomic_thread_fence(std::memory_order_acq_rel);
std::atomic_signal_fence(std::memory_order_acquire);
atomic_thread_fence
:在当前线程插入一个全局的内存屏障,配合任意非‑relaxed 原子操作使用。atomic_signal_fence
:仅禁止编译器重排,不生成 CPU 指令屏障,常用于与信号处理配合。
六、锁‑自由(Lock‑Free)特性
每个 std::atomic<T>
都有静态成员:
static constexpr bool is_lock_free = /* 编译时可知 */;
bool f() const noexcept { return this->is_lock_free; }
- 如果
is_lock_free
为true
,则所有操作都映射到无锁硬件指令;否则可能内部加锁。 - 检查:可在运行时用
atomic_obj.is_lock_free()
确认。
七、示例:无锁单生产者单消费者队列(简化)
template<typename T, size_t N>
class SPSCQueue {
std::atomic<size_t> head{0}, tail{0};
T buffer[N];
public:
bool enqueue(const T& v) {
size_t t = tail.load(std::memory_order_relaxed);
size_t next = (t + 1) % N;
if (next == head.load(std::memory_order_acquire))
return false; // 满
buffer[t] = v;
tail.store(next, std::memory_order_release);
return true;
}
bool dequeue(T& out) {
size_t h = head.load(std::memory_order_relaxed);
if (h == tail.load(std::memory_order_acquire))
return false; // 空
out = buffer[h];
head.store((h + 1) % N, std::memory_order_release);
return true;
}
};
- 使用
relaxed
+acquire/release
内存序实现正确同步,且无任何锁。
八、实践建议
- 尽可能用最弱内存序
- 若只需原子性,不关心同步,用
memory_order_relaxed
;否则常用acquire
/release
。
- 若只需原子性,不关心同步,用
- 慎用
memory_order_seq_cst
- 全序一致最安全,但性能开销最大;仅在多线程交互非常复杂时使用。
- 优先高层并发结构
- 直接使用原子操作实现复杂数据结构代价高,推荐先考虑
<mutex>
、并发队列框架、或第三方无锁容器。
- 直接使用原子操作实现复杂数据结构代价高,推荐先考虑
- 验证锁‑自由
- 对性能敏感时,检查
is_lock_free
,确保硬件支持;否则回退到互斥锁。
- 对性能敏感时,检查
- 避免 ABA 问题
- 当用 CAS(compare_exchange)更新指针时,要注意引用计数、版本号或双宽 CAS(如
std::atomic<std::uintptr_t>
加版本)。
- 当用 CAS(compare_exchange)更新指针时,要注意引用计数、版本号或双宽 CAS(如
- 测试与工具
- 使用 ThreadSanitizer、Helgrind 等检测数据竞争;用 litmus 测试内存序模型。
通过以上对 <atomic>
中原子类型、操作、内存序、屏障及锁‑自由特性的全面讲解与示例,希望能帮助你在性能关键的并发场景中安全、正确地运用无锁编程技巧。祝编码顺利!