无锁哈希表通过std::atomic与cas实现线程安全:桶数组64字节对齐防伪共享,node结构体alignas(64),murmurhash3哈希+位与取模;插入用weak cas头插,查找用acquire遍历,删除需双cas标记后延迟回收。

实现一个线程安全、高吞吐的无锁哈希表,需绕过互斥锁带来的阻塞与调度开销,直接利用 std::atomic 和 CAS(Compare-And-Swap)原语构建插入、查找、删除逻辑,在多核环境下保持缓存行友好与 ABA 风险可控。
设计核心数据结构与内存布局
定义桶数组为 std::atomic<node></node> 类型的动态分配数组,每个桶头指针必须对齐到 64 字节边界以避免伪共享;Node 结构体中 next 指针使用 std::atomic<node></node>,且声明时添加 alignas(64) 确保单节点独占缓存行。
哈希函数采用 MurmurHash3_x64_64,输入 key 的字节序列,输出 64 位哈希值;取模运算用位与替代(桶数量固定为 2 的幂),即 index = hash & (capacity - 1)。
【capacity 必须是 2 的整数次幂,否则位与结果不等价于取模】
无锁插入:CAS 循环 + 头插法 + 内存序控制
方法一:基础 CAS 插入路径
1. 计算 key 对应桶索引 index → 读取 buckets[index].load(std::memory_order_acquire) 获取当前头结点 ptr。
2. 构造新节点 newNode,设置 newNode->next = ptr。
3. 执行 CAS:buckets[index].compare_exchange_weak(ptr, newNode, std::memory_order_release, std::memory_order_acquire)。若失败,ptr 被更新为最新头指针,回到第 2 步重试。
这一步必须用 weak 版本,因 x86 上 compare_exchange_strong 在循环中可能因 spurious failure 导致性能劣化;ARM 架构下 weak 更贴近硬件语义。
方法二:带键重复检查的插入
在 CAS 前遍历链表,比对 key 是否已存在;若存在,直接返回 false;否则继续头插。注意遍历时所有 next 加载必须用 std::memory_order_acquire,防止编译器或 CPU 重排导致读到未初始化的 next 指针。
无锁查找:单向遍历 + acquire 语义保障可见性
读取 buckets[index].load(std::memory_order_acquire) 得到链表头 → 逐个比较节点 key,直到匹配或遇到 nullptr。
每次访问 node->next 都需调用 load(std::memory_order_acquire),确保能看到之前任意线程对该节点 next 的写入结果;不加 memory_order 会导致读取到陈旧值甚至未定义行为。
查找过程全程不修改任何原子变量,无需 CAS,也不触发内存屏障写操作,因此延迟极低。
无锁删除:双 CAS 原子替换 + 延迟回收机制
第一步:标记待删节点
遍历链表定位目标节点 prev → 使用 compare_exchange_weak 尝试将 prev->next 从 targetNode 替换为 targetNode->next,但仅当 targetNode->next 未被标记时才成功;若失败,说明其他线程已开始处理该节点,放弃本次删除。
第二步:设置已删除标记
对 targetNode->next 执行 fetch_or(1, std::memory_order_acq_rel),将最低位设为 1,表示该节点已被逻辑删除;此后所有查找操作遇到该标记位即跳过该节点。
【必须先完成第一步的 CAS 替换,再执行 fetch_or 标记,顺序不可颠倒,否则其他线程可能遍历到孤立的已标记节点】
第三步:交由用户侧或专用 reclaimer 线程异步释放内存,禁止在删除路径中直接 delete targetNode。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











