裸指针无法保证原子性、内存序和ABA防护,必须用std::atomic<Node*>配合CAS循环、显式内存序及延迟回收才能正确实现无锁链表。

为什么直接用裸指针写非阻塞算法几乎必然出错
裸指针本身不提供原子性、内存序或 ABA 问题防护,std::atomic<T*> 才是 C++ 中实现非阻塞数据结构的起点。你不能靠 int* p 加个 while(!done) { /* 自旋 */ } 就叫非阻塞——那只是忙等,且极易因重排序、缓存不一致或指针悬空导致未定义行为。
真正可落地的非阻塞逻辑必须依赖 std::atomic<Node*>(或 std::atomic<uintptr_t> 配合 tagged pointer)+ 显式内存序 + CAS 循环。
如何用 std::atomic<Node*> 实现无锁栈 push
这是最典型的入门案例:每个节点包含 next 指针,栈顶用原子指针维护。关键不是“用指针”,而是“用原子指针做 CAS”。
-
head必须声明为std::atomic<node>&</node>,不能是Node*加锁模拟 - CAS 失败必须重读当前
head值再重试,否则会覆盖其他线程刚写入的更新 -
std::memory_order_relaxed在读取head时可用,但compare_exchange_weak至少需memory_order_acq_rel - 注意节点分配必须线程安全(例如用
new或无锁内存池),且不能在 CAS 成功后立即delete被替换的旧头节点(需延迟回收)
struct Node {
int data;
Node* next;
};
<p>std::atomic<Node*> head{nullptr};</p><p><span>立即学习</span>“<a href="https://pan.quark.cn/s/6e7abc4abb9f" style="text-decoration: underline !important; color: blue; font-weight: bolder;" rel="nofollow" target="_blank">C++免费学习笔记(深入)</a>”;</p><div class="aritcle_card flexRow">
<div class="artcardd flexRow">
<a class="aritcle_card_img" href="/xiazai/gongju/2823" title="C++14"><img
src="https://img.php.cn/upload/manual/001/431/639/6ac8b33c327c4749.png" alt="C++14" ></a>
<div class="aritcle_card_info flexColumn">
<a href="/xiazai/gongju/2823" title="C++14">C++14</a>
<p>C++14 对 C++11 的修正与增强版本,适合旧系统维护和较老工具链兼容。</p>
</div>
<a href="/xiazai/gongju/2823" title="C++14" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span> </a>
</div>
</div><p>void push(int x) {
Node<em> node = new Node{x, nullptr};
Node</em> old_head = head.load(std::memory_order_acquire);
do {
node->next = old_head;
} while (!head.compare_exchange_weak(old_head, node,
std::memory_order_acq_rel,
std::memory_order_acquire));
}为什么 compare_exchange_weak 比 strong 更常用
弱版本允许伪失败(spurious failure),即值未变也返回 false,但它在多数架构(x86/ARM)上生成更轻量的指令;强版本则保证只在值真不同时失败。实际写循环时,弱版配合 do-while 是标准模式。
- 伪失败不会破坏正确性,因为循环会重读并重试
- x86 上
compare_exchange_weak和strong汇编相同,ARM 上 weak 可能省去一条条件分支 - 若在非循环上下文中使用(极少见),才考虑
strong - 永远不要忽略
compare_exchange_*的返回值——它决定了是否要重试
ABA 问题不是理论,它会在真实链表操作中触发崩溃
当一个节点 A 被弹出(head 变成 B),又被释放、重用并重新插回栈顶(head 再次变成 A),CAS 会误认为“没变过”,从而跳过本该检测到的中间修改。裸指针完全无法感知这种重用。
解决方案不是避免重用,而是让指针携带版本号:
- 用
std::atomic<uintptr_t>存储 “低 48 位指针 + 高 16 位计数”,每次 CAS 同时更新两者 - 或使用
std::atomic<std::pair<Node*, size_t>>(需自定义特化is_lock_free) - 更稳妥的做法是结合 Hazard Pointer 或 RCU 延迟回收节点,从根源上杜绝 ABA
没处理 ABA 的无锁栈,在高并发长运行下大概率 crash 或静默数据损坏,而且复现困难。

















