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 内存序实现正确同步,且无任何锁。

八、实践建议

  1. 尽可能用最弱内存序
    • 若只需原子性,不关心同步,用 memory_order_relaxed;否则常用 acquire/release
  2. 慎用 memory_order_seq_cst
    • 全序一致最安全,但性能开销最大;仅在多线程交互非常复杂时使用。
  3. 优先高层并发结构
    • 直接使用原子操作实现复杂数据结构代价高,推荐先考虑 <mutex>、并发队列框架、或第三方无锁容器。
  4. 验证锁‑自由
    • 对性能敏感时,检查 is_lock_free,确保硬件支持;否则回退到互斥锁。
  5. 避免 ABA 问题
    • 当用 CAS(compare_exchange)更新指针时,要注意引用计数、版本号或双宽 CAS(如 std::atomic<std::uintptr_t> 加版本)。
  6. 测试与工具
    • 使用 ThreadSanitizer、Helgrind 等检测数据竞争;用 litmus 测试内存序模型。

通过以上对 <atomic> 中原子类型、操作、内存序、屏障及锁‑自由特性的全面讲解与示例,希望能帮助你在性能关键的并发场景中安全、正确地运用无锁编程技巧。祝编码顺利!

类似文章

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注