不能直接用 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};</p><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));
}pop_front 需要处理内存释放时机,不能直接 delete
无锁结构中,一个节点可能正被其他线程通过尚未完成的 CAS 操作引用。直接 delete 会导致悬垂指针和未定义行为。正确做法是:先用 CAS 将 head 替换为 head->next,拿到旧 head 后,**不立即释放**,而是交由 Hazard Pointer、RCU 或 epoch-based reclamation 等机制延后回收。最简实践是改用 std::shared_ptr 管理节点(虽非严格无锁,但可避免手动内存管理错误):
立即学习“C++免费学习笔记(深入)”;
-
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 或经过充分测试的并发容器。


















