无锁哈希表不能用裸指针实现,因其无法保证多线程下原子性,会引发aba问题、内存重排和use-after-free;必须用std::atomic+ cas+内存序控制,且内存回收(如hazard pointer)比插入更复杂。

无锁哈希表为什么不能靠裸指针搞定
直接用 int* 或 Node* 手动管理哈希桶和节点,无法保证多线程下读写原子性——哪怕你用 std::atomic<node></node> 包裹指针,仍会遇到 ABA 问题、内存重排、释放后访问(use-after-free)等底层陷阱。C++ 标准库不提供无锁哈希表实现,std::unordered_map 是完全加锁的;自行实现必须依赖 std::atomic + CAS(compare_exchange_weak)+ 内存序控制,裸指针本身不具备同步语义。
核心结构:每个桶用 std::atomic<node></node> 管理链表头
典型无锁哈希表(如基于分离链接法)中,每个桶是一个原子指针,指向该桶内单向链表的头节点。插入时需 CAS 更新头指针;查找只需原子读;删除则需更谨慎的标记-清除(tagged pointer)或 hazard pointer 配合。
- 节点定义必须对齐且包含原子指针:
struct Node { int key; int value; std::atomic<node> next{nullptr}; };</node> - 桶数组声明为
std::vector<:atomic>></:atomic>,初始化时每个元素为nullptr - 插入逻辑关键在 CAS 循环:
Node* expected = bucket.load(std::memory_order_acquire); do { new_node->next.store(expected, std::memory_order_relaxed); } while (!bucket.compare_exchange_weak(expected, new_node, std::memory_order_release, std::memory_order_acquire)); - 注意:
compare_exchange_weak可能虚假失败,必须用 do-while 循环重试
std::memory_order 选错会导致静默崩溃
内存序不是性能优化选项,而是正确性前提。在无锁结构中混用错误序会导致编译器/CPU 重排破坏操作顺序,比如插入节点后其他线程看到 next 指针已更新但 key/value 还是未初始化值。
- 桶指针的
load()至少用std::memory_order_acquire(确保后续读取节点字段不被提前) - 桶指针的
store()或 CAS 成功路径至少用std::memory_order_release(确保节点字段写入不被延后) - CAS 失败路径用
std::memory_order_acquire即可,避免过度开销 - 绝不能对桶指针使用
std::memory_order_relaxed—— 除非你只做单线程测试
真正难的是内存回收,不是插入逻辑
无锁结构最常崩在内存释放:一个线程刚把节点从链表摘下,另一个线程还在遍历旧指针。RCU、hazard pointer、epoch-based reclamation(EBR)这些机制远比 CAS 插入复杂。例如用 hazard_pointer 库时,每次遍历前必须注册当前 hazard pointer,遍历完再清除;否则回收器可能在遍历中途释放节点。
- 不要尝试自己写引用计数回收——无锁环境下计数本身就需要 CAS,且易漏减
- 生产环境建议直接用成熟库:
libcds的cds::container::MichaelHashMap或Folly的AtomicUnorderedMap,它们已处理好内存模型与回收细节 - 若必须手写,先实现“只增不删”版本验证逻辑,再逐步加入安全删除——后者几乎必然引入额外数据结构和线程局部状态
没处理好内存回收的“无锁哈希表”,跑得越快,崩溃越晚、越难复现。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











