不能直接用std::forward_list做无锁,因其操作非线程安全且未暴露原子接口;必须自定义node结构,含std::atomic、禁止虚函数/析构逻辑,并配合hazard pointer或带版本号指针解决aba与内存回收问题。

为什么不能直接用 std::forward_list 做无锁?
因为 std::forward_list 的所有操作(插入、删除、遍历)都依赖内部互斥量或非原子访问,不是线程安全的——更关键的是,它根本没暴露底层节点指针和原子操作接口。你要做无锁,就得自己管理节点内存、用 std::atomic 修饰 next 指针,并严格遵循 ABA 防御和内存序规则。
核心结构体怎么写才安全?
节点必须是固定布局、无虚函数、无析构逻辑(避免 delete 时竞争),next 字段必须是 std::atomic<node></node>,且初始化为 nullptr。别用 std::shared_ptr 或 std::unique_ptr,它们的引用计数操作不是无锁友好的。
示例:
struct Node {
int data;
std::atomic<node> next{nullptr};
// 注意:禁止在这里放 std::string 或其他可能抛异常/调用 new 的成员
};</node>
-
next必须用std::atomic<node></node>,不能用Node*+ 手动 fence - 构造函数只做 trivial 初始化,不分配额外资源
- 节点生命周期由外部(如 Hazard Pointer 或 RCU)管理,不是 new/delete 交替触发
push_front 怎么避免 ABA 问题?
直接 CAS head 是经典 ABA 风险点:线程 A 读到 head == p,被抢占;线程 B 把 p 删除并重用为新节点;A 恢复后仍认为 p 是旧 head,CAS 成功但链表逻辑损坏。
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
解决方式不是加锁,而是用「带版本号的指针」——把指针低几位空出来存 tag(比如用 64 位指针,低 3 位当 tag)。C++20 起可用 std::atomic<uintptr_t></uintptr_t> 手动拆解,或用第三方库如 libcds 的 atomic_tagged_ptr。
- 每次 CAS 前递增 tag,即使指针值不变,tag 变了 CAS 就失败
- 不要依赖
std::atomic<node>::compare_exchange_weak</node>默认行为,它不防 ABA - 如果业务场景确定不会重用刚释放的节点(比如用内存池预分配且永不归还),可略过 tag,但这是危险假设
pop_front 为什么必须配合 Hazard Pointer?
无锁 pop 的典型模式是:读 head → CAS head 到 head->next → 然后 delete 旧 head。但这里存在竞态:另一个线程可能正遍历到该节点,你一 delete,它就访问野指针。
所以不能裸 delete,得用 Hazard Pointer 记录“当前有哪些线程正在访问哪些节点”。只有当所有 hazard list 都没引用该节点时,才能回收内存。
- Hazard Pointer 本身要无锁实现,通常每个线程一个
std::atomic<node></node>全局数组 - 遍历前把自己的 hazard 指针设为当前访问节点;遍历完清零
- 回收线程定期扫描所有 hazard 指针,确认无引用后再真正 free
- 别用 epoch-based reclamation(如 urcu)除非你控制整个程序生命周期,否则易泄漏
真正的难点不在链表逻辑,而在内存回收策略的选择和正确实现——多数人卡在这一步,而不是 push/pop 的 CAS 写法。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!










