跳表节点设计必须使用std::atomic指针并配合适当内存序与安全回收机制,否则并发修改会导致指针损坏、数据丢失或内存错误。

跳表节点设计必须带原子指针,否则并发修改会出错
跳表在并发场景下最常踩的坑是节点指针非原子性——比如用 Node* 存储 next 指针,多个线程同时 CAS 更新时,可能只更新了低 32 位(在某些平台或编译器优化下),导致指针损坏。C++11 起必须用 std::atomic<node></node>,且初始化需用 ATOMIC_VAR_INIT 或 std::atomic<node>(nullptr)</node>。
实际编码中还要注意:节点构造后不能直接暴露给其他线程,得先用 std::atomic_thread_fence(std::memory_order_release) 确保所有字段写入完成;读取时对应加 acquire 栅栏。否则编译器或 CPU 可能重排指令,让其他线程看到未初始化的字段值。
- 每个层级的
next字段都得是独立的std::atomic<node></node>,不能共用一个指针数组加原子索引 - 节点内存必须用
new(或std::make_unique)分配,不可栈分配或静态分配——生命周期无法保证 - 删除节点时不能立即
delete,要等所有线程确认不再访问(通常配合 hazard pointer 或 epoch-based reclamation)
插入操作必须用“无锁+回退”双层校验,单次 CAS 不够
跳表插入不是简单地找到位置然后 CAS next 指针。因为多层结构存在“中间层已连、底层未连”的瞬态不一致状态,其他线程若在此时查找,可能漏掉刚插入的节点。
标准做法是:从最高层开始向下遍历,记录每层的前置节点(update[i] 数组),然后从底层(level 0)向上逐层 CAS。但关键点在于:每次 CAS 后必须重新检查该层链是否仍符合预期——即用 load(memory_order_acquire) 验证前置节点的 next[i] 是否仍指向我们刚设的目标,否则说明有竞争,需重试。
- 不能省略任一层的 CAS 校验,尤其 level 0 是数据真实所在层,漏校验会导致 key 丢失
- 随机层数(
randomLevel())应在插入前确定,并限制最大层数(如 ≤32),避免内存爆炸 - 若某次 CAS 失败,整个插入必须从头开始(不是仅重试某一层),否则
update数组失效
std::shared_ptr 不能直接用于跳表节点管理
用 std::shared_ptr 包裹节点看似能自动管理内存,但在高并发跳表中会引发严重问题:引用计数本身是原子操作,但每次 load() 或 store() 都带 full barrier,性能开销大;更致命的是,两个线程可能各自持有一个 shared_ptr 指向同一节点,导致析构时机不可控,与 hazard pointer 等回收机制冲突。
真正可行的方案是分离“逻辑存活”和“物理销毁”:节点被标记为删除后,由专门的 reclamation 线程延迟回收;而查找/插入线程只通过原始原子指针访问,配合 hazard pointer 记录当前正在访问哪些节点地址。
- 禁止在节点内存储
std::shared_ptr<node></node>形成循环引用(例如 backptr) - 如果非要 RAII,可封装一个轻量
GuardedNodePtr类,内部只存裸指针 + 注册/注销 hazard pointer,不碰引用计数 - Clang/GCC 下注意
-march=native可能影响std::atomic<node></node>的实现方式(如是否用lock xchg),压测时需验证
查找函数 find() 必须支持“弱一致性”语义,别强求实时可见
跳表的 find(key) 默认不应阻塞或重试,而是返回“某一时刻的快照结果”。这意味着:即使另一个线程刚插入该 key,当前查找也可能返回 not-found——这反而是正确行为。强行加锁或无限重试会让读变慢,违背跳表初衷。
真正需要严格一致性的场景(如事务中的 read-your-write),应由上层逻辑维护 thread-local 缓存或 write-after-read 依赖链,而不是让 find() 自己去同步。
-
find()内部遍历各层时,每步next.load(memory_order_acquire)就够了,无需seq_cst - 返回值建议用
std::optional<:reference_wrapper value>></:reference_wrapper>或裸指针 + bool,避免拷贝 value - 如果业务允许,可在查找不到时返回最近的 predecessor 节点指针,方便后续 insert 做局部重试优化
tsan 和自定义 allocators 验证所有节点生命周期。C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











