无锁哈希表通过原子指针插入、标记指针回收、分段扩容、pause降载及位压缩存储实现高并发安全。具体包括:CAS插入节点;低位标记逻辑删除并惰性释放;分段桶数组原子切换扩容;失败重试时插入pause指令;32位键值合并为64位原子操作。

一、基于原子指针的节点链表无锁插入
在多线程环境下向哈希桶中插入新节点时,需避免使用互斥锁,转而依赖 std::atomic
1、为每个哈希桶维护一个 std::atomic
2、构造新节点 node,并设置其 next 字段为当前 head_ptr.load(std::memory_order_acquire) 的值。
3、调用 head_ptr.compare_exchange_weak(expected, node, std::memory_order_acq_rel, std::memory_order_acquire),其中 expected 为步骤2中读取的旧值。
立即学习“C++免费学习笔记(深入)”;
4、若 compare_exchange_weak 返回 false,说明 head_ptr 已被其他线程更新,则将 expected 更新为当前 head_ptr.load(),重新执行步骤2至步骤3,直至成功。
二、使用标记指针实现安全内存回收(Hazard Pointer 变体)
无锁结构中节点可能被其他线程正在遍历,直接 delete 会导致悬垂指针。通过标记指针低位存储删除状态,并结合原子读写,使插入与删除操作可并发且内存安全。
1、定义指针类型为 std::atomic
2、删除节点前,先用 CAS 将指针最低位置 1,表示该节点逻辑删除;此操作需保证仅执行一次,失败则跳过。
3、后续查找线程在遍历时检查当前节点指针的最低位:若为 1,则跳过该节点并继续遍历下一个。
4、物理释放由独立的惰性回收线程执行,仅当确认无任何活跃线程持有该节点指针时,才调用 delete。
三、分段哈希桶数组 + 原子大小扩容机制
为支持运行时扩容且不中断服务,将哈希表底层划分为多个独立的桶段(Segment),每段对应一个 std::atomic
1、初始化时创建固定数量的 Segment,每个 Segment 包含固定长度的桶指针数组(如 256 个)。
2、当某段负载因子超过阈值,触发该段扩容:分配新桶数组,将原数组中所有非空链表节点 rehash 到新数组。
3、使用 compare_exchange_strong 尝试将 segment.bucket_array 从旧数组指针更新为新数组指针。
4、若 CAS 失败,说明其他线程已成功扩容,则丢弃新数组并复用已有结构;否则,完成迁移后释放旧数组。
四、CAS 循环中嵌入 pause 指令降低 CPU 占用
在 x86/x64 平台上,密集的 compare_exchange_weak 自旋会持续占用核心资源。插入或查找循环中插入 _mm_pause() 内置指令,可提示处理器当前处于忙等待状态,减少功耗与总线争用。
1、在 compare_exchange_weak 失败后的重试循环体内,插入 _mm_pause() 调用(需包含
2、避免在单次 CAS 前重复调用多次 pause,仅每次失败后调用一次即可。
3、对于非 x86 架构(如 ARM),使用 __builtin_arm_pause() 或对应平台的等效指令替代。
4、验证 pause 插入位置不影响正确性:仅置于失败分支中,不干扰内存序语义与原子操作本身。
五、键值对存储采用 std::atomic 位压缩优化
当键与值均为 32 位整型时,可将其合并存入单个 std::atomic
1、定义 union KeyValUnion { uint64_t raw; struct { uint32_t key; uint32_t val; }; },确保内存布局一致。
2、使用 std::atomic
3、插入时构造 KeyValUnion 实例,设置 key 和 val 字段,再以 raw 值执行 CAS 写入。
4、查找时读取 raw 值,解析出 key 字段比对;匹配成功后提取 val 字段返回。


















