跳转到主内容
websoft网络软件专家 - 深耕网络技术,打造实用软件!

C++中的原子操作(std::atomic)是什么?(如何实现无锁编程)

std::atomic 是 C++11 提供的原子操作模板类,仅保证单变量读-改-写操作的原子性,不能替代互斥锁实现多字段同步;它支持 load/store/fetch_add 等原子操作,但 ++a 是否真正原子依赖硬件指令,且需谨慎选择内存序以平衡正确性与性能。 std::atomic 是什么,它真能帮你避开互斥锁?
std::atomic
是 C++11 引入的模板类,用来对单个变量做「不可分割」的读-改-写操作。它不等于“无锁编程”的全部,但确实是无锁数据结构最基础的砖块。关键点在于:它只保证单个对象的原子性,不是魔法——比如对
std::atomic
的
load()
、
store()
、
fetch_add()
是原子的;但
a = b + c
(哪怕 a/b/c 都是 atomic)依然不是原子操作,中间可能被中断。 哪些操作真正原子?哪些只是“看起来像”? 常见误区是以为
++a
(其中
a
是
std::atomic
)天然线程安全——其实它依赖底层是否提供对应指令。x86 上
fetch_add(1)
通常编译为
lock xadd
,是原子的;但 ARMv7 没有原生 fetch_add,可能退化为 LL/SC 循环(即 compare_exchange_weak 重试),失败时需手动处理。
load()
和
store()
默认用
memory_order_seq_cst
,强顺序,但开销略大
fetch_add()
、
fetch_sub()
、
compare_exchange_weak()
等才是构建无锁逻辑的核心,必须显式检查返回值
operator=
调用的是
store()
,
operator++
调用的是
fetch_add(1)
,但它们的内存序仍受模板参数影响,别默认 为什么直接用 std::atomic 做计数器没问题,但做队列就大概率出错? 因为无锁队列要同时维护 head/tail 两个指针,并保证「取节点 + 移动 head」这一连串动作的原子性——而
std::atomic
不支持多字段原子更新。你不能靠两个
std::atomic
安全实现一个 lock-free queue,必须用
compare_exchange_weak()
配合状态重试,或引入 ABA 问题防护(如带版本号的指针)。 典型错误写法: 立即学习 “ C++免费学习笔记(深入) ”; C知道 CSDN推出的一款AI技术问答工具 下载
Node* old_head = head.load(); Node* next = old_head->next; head.store(next); // ❌ 中间 old_head 可能已被其他线程释放
正确做法是用循环 +
compare_exchange_weak()
验证 head 未变再更新:
Node* expected = head.load(); Node* desired; do { desired = expected->next; } while (!head.compare_exchange_weak(expected, desired));
内存序(memory_order)选错会导致什么? 选太松(如
memory_order_relaxed
)可能让编译器或 CPU 重排指令,导致其他线程看到「部分更新」的状态;选太紧(如全用
memory_order_seq_cst
)又会抑制优化,尤其在 ARM/PowerPC 上性能损失明显。 计数器累加常用
memory_order_relaxed
(只要数值不错就行) 标志位(如
ready
)用
memory_order_acquire
(load)+
memory_order_release
(store)配对,确保其前后的内存访问不跨序
compare_exchange_weak()
默认是
seq_cst
,但若你已用 acquire/release 控制好依赖,可显式降级为
memory_order_acq_rel
真正难的从来不是写对一个
fetch_add
,而是理清多个原子变量之间的同步关系和数据依赖——这里漏掉一个
acquire
,整个无锁结构就可能在某次内核升级后突然失效。

相关文章