C++如何使用std::atomic实现无锁编程?(原子操作)

std::atomic 是 C++11 提供的无锁原子操作工具,支持 load/store/exchange/compare_exchange 等操作,需合理选用 memory_order 并注意 lock-free 性、ABA 问题及正确性验证。

std::atomic 是 C++11 引入的核心工具,用于在多线程环境中安全地读写共享变量,无需互斥锁(mutex),从而实现无锁(lock-free)编程。它的关键在于:所有操作都是原子的,不会被线程调度打断,且编译器和 CPU 会按需插入内存屏障,防止重排序导致的数据竞争。

基础用法:声明与基本操作

std::atomic 封装一个类型为 T 的变量(T 需为 trivially copyable,如 int、bool、指针等)。常用操作包括 load()、store()、exchange() 和 compare_exchange_weak()/strong():

  • load():原子读,等价于 operator T(),默认 memory_order_seq_cst(最强顺序)
  • store(val):原子写,等价于 operator=(val)
  • exchange(val):原子地将原值替换为 val,并返回旧值
  • compare_exchange_weak(expected, desired):若当前值等于 expected,则设为 desired 并返回 true;否则将当前值写入 expected 并返回 false(可能伪失败,需循环重试)

实现无锁计数器(典型例子)

下面是一个线程安全的无锁自增计数器,不依赖 mutex:

std::atomic counter{0};

void increment() { counter.fetch_add(1, std::memory_order_relaxed); }

int get_value() { return counter.load(std::memory_order_acquire); }

这里用 fetch_add 替代 read-modify-write 手动实现,避免竞态。注意:memory_order_relaxed 适用于仅需原子性、不依赖其他内存操作顺序的场景(如计数器);若需同步其他数据,应升级为 acquire/release 或保持默认 seq_cst

实现无锁栈(push/pop)

无锁栈依赖 compare_exchange_weak 实现 ABA 问题规避(通常配合 tag pointer 或 hazard pointer,简单版可先忽略 ABA):

struct Node {
    int data;
    Node* next;
};

std::atomic head{nullptr};

void push(int data) { Node* node = new Node{data, nullptr}; node->next = head.load(std::memory_order_relaxed); while (!head.compare_exchange_weak(node->next, node, std::memory_order_release, std::memory_order_relaxed)) { // 若 head 已变,node->next 被自动更新为当前 head,继续重试 } }

pop 类似,但需小心处理内存释放(建议搭配 std::shared_ptr 或专用内存回收机制,否则易出现 use-after-free)。

注意事项与常见陷阱

无锁 ≠ 无脑高效。实际使用中要注意:

  • 不是所有 std::atomic 都是 lock-free:可用 is_lock_free() 检查,例如某些平台下 atomic 可能回退到内部锁
  • 过度依赖 seq_cst 会显著降低性能;应根据同步需求选择合适 memory_order(如 producer-consumer 场景常用 release/acquire)
  • compare_exchange_weak 可能因硬件原因“假失败”,必须放在 do-while 或 while 循环中重试;strong 版本不假失败但可能更慢
  • 无锁结构的正确性极难验证,建议优先使用成熟库(如 folly::AtomicSharedPtr、boost::lockfree)或标准容器(std::queue 不是无锁的,勿误用)