不能直接用 std::atomic 操作链表节点指针,因其仅能原子读写指针本身,而插入/删除需同时更新前驱节点 next 和新节点 next,且存在 aba 问题;必须结合版本号或智能指针等机制保障正确性。

为什么不能直接用 std::atomic<t></t> 操作链表节点指针
因为 std::atomic<t></t> 只能原子地读写指针本身,但插入或删除节点时需同时更新两个位置:前驱节点的 next 字段 + 新节点的 next 字段。若仅靠单个原子指针,会遇到 ABA 问题(例如:节点 P 被弹出 → 内存被释放 → 新节点恰好复用同一地址 → CAS 成功但逻辑错误)。所以必须用 std::atomic<uintptr_t></uintptr_t> 或 std::atomic<:uintptr_t></:uintptr_t> 手动拼装「指针 + 版本号」,或者借助 std::atomic<:shared_ptr>></:shared_ptr>(但后者有引用计数开销,且 C++11/14 中 std::shared_ptr 的原子操作不是无锁的)。
push_front 必须用 compare_exchange_weak 循环重试
这是最典型的无锁插入模式:读取当前 head,构造新节点并设置其 next 指向旧 head,再尝试用 CAS 更新 head。失败说明 head 已被其他线程修改,需重读重试。
- 必须用
compare_exchange_weak(而非_strong),避免在某些平台因伪失败导致死循环 - 新节点的
next字段必须在 CAS 前就设为当前读到的 head 值,否则可能形成环或跳过节点 - 不要在 CAS 失败后直接 return —— 必须循环,否则插入会丢失
struct Node {
int data;
std::atomic<node> next{nullptr};
};
<p>std::atomic<node> head{nullptr};</node></p><div class="aritcle_card flexRow artxards">
<div class="artcardd flexRow">
<a class="aritcle_card_img" rel="nofollow" href="/xiazai/gongju/2823" title="C++14"><img
src="https://img.php.cn/upload/manual/001/431/639/6ac8b33c327c4749.png" alt="C++14" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
<div class="aritcle_card_info flexColumn">
<a rel="nofollow" href="/xiazai/gongju/2823" title="C++14" class="overflowclass">C++14</a>
<p class="overflowclass">C++14 对 C++11 的修正与增强版本,适合旧系统维护和较老工具链兼容。</p>
</div>
<a rel="nofollow" href="/xiazai/gongju/2823" title="C++14" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
</a>
</div>
</div>
<p>void push_front(int x) {
Node<em> n = new Node{x};
Node</em> expected;
do {
expected = head.load();
n->next.store(expected, std::memory_order_relaxed);
} while (!head.compare_exchange_weak(expected, n,
std::memory_order_release,
std::memory_order_relaxed));
}</p></node>
pop_front 需要处理内存释放时机,不能直接 delete
无锁结构中,一个节点可能正被其他线程通过尚未完成的 CAS 操作引用。直接 delete 会导致悬垂指针和未定义行为。正确做法是:先用 CAS 将 head 替换为 head->next,拿到旧 head 后,**不立即释放**,而是交由 Hazard Pointer、RCU 或 epoch-based reclamation 等机制延后回收。最简实践是改用 std::shared_ptr 管理节点(虽非严格无锁,但可避免手动内存管理错误):
-
std::atomic<:shared_ptr>></:shared_ptr>在 C++20 起保证 lock-free(需检查is_lock_free()) - pop 时仍需循环 CAS:读 head → 构造新 head(
old_head->next)→ CAS 更新 → 若成功,返回old_head(其析构自动触发内存释放) - 注意:C++11/14 中
std::shared_ptr的原子操作底层可能用锁,实际是否无锁取决于标准库实现
实际使用前务必验证 is_lock_free()
不同平台、不同编译器、甚至不同 std::atomic 模板参数,都可能导致原子操作退化为内部互斥锁。比如:std::atomic<:shared_ptr>></:shared_ptr> 在 x86-64 上常是 lock-free,但在 ARM 或某些嵌入式平台可能不是。
- 运行期检查:
if (!head.is_lock_free()) { /* fallback or abort */ } - 编译期断言:
static_assert(std::atomic<node>::is_always_lock_free, "Node* atomic must be lock-free");</node> - 即使
is_lock_free()返回 true,也不能保证整个算法无锁——ABA 和内存重排序仍是核心难点
真正安全的无锁链表远不止原子操作拼接;版本号、内存屏障顺序、回收机制缺一不可。初学者从 push_front 单点入手可以理解模式,但生产环境请优先考虑 boost::lockfree::slist 或经过充分测试的并发容器。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!










