无锁跳表在C++标准库中不存在,需自行实现或使用libcds、Folly等第三方库;libcds提供cds::container::SkipListMap,支持无锁有序操作但不支持真正无锁范围查询。

无锁跳表在C++中不是标准库组件,得自己实现或用成熟第三方
标准C++库没有 concurrent_skip_list 或类似容器。STL 的 std::map 和 std::set 是有序但完全加锁的;boost::container::flat_map 无锁但不支持并发插入。真要“超高并发 + 有序 + 无锁”,只能靠自己实现或引入经过验证的第三方库。
推荐两个实际可用的选项:
-
libcds(Concurrent Data Structures library):提供cds::container::SkipListMap,基于 Michael 无锁跳表算法,支持多种内存模型和 GC 策略,生产环境有使用案例 -
Folly(Facebook 开源):提供folly::AtomicUnorderedMap,但注意——它**不是有序的**;Folly 没有公开维护的无锁有序 map,别被名字误导
自行实现无锁跳表极易出错:ABA 问题、内存回收(hazard pointer / epoch-based reclamation)、层级生成一致性、删除时的多层原子更新……没几个月打磨很难稳定。
libcds 中 SkipListMap 的基本用法和关键配置
以 cds::container::SkipListMap 为例,它默认使用 cds::gc::HP(hazard pointer 垃圾回收),这是最稳妥的选择。以下是最小可行代码片段:
#include <cds>
#include <cds><p>// 必须在主线程初始化 GC
cds::gc::HP hpGC;</p>
<p>int main() {
// key=int, value=std::string,升序比较
cds::container::SkipListMap<int std::string> map;</int></p>
<pre class="brush:php;toolbar:false;">map.insert( {42, "answer"} );
map.insert( {1, "first"} );
std::string* p = map.get(42); // 线程安全读取
if (p) {
std::cout <p>}
</p>
几个关键点:
- 必须显式初始化 GC(如
cds::gc::HP),否则运行时报cds::gc::not_initialized -
insert()和get()是线程安全的,但get()返回的是带生命周期保护的指针,用完需调用retire()或依赖作用域自动释放(取决于 GC 模式) - 不支持 STL 风格的迭代器遍历——因为无锁结构无法提供强一致快照;若需遍历,应改用
for_each回调或接受近似一致性
性能瓶颈不在跳表本身,而在内存回收与 CPU 缓存行竞争
实测发现,当 QPS 超过 50 万/秒后,libcds::SkipListMap 的吞吐增长会明显放缓,热点常出现在:
- hazard pointer 的全局 registry 锁争用(尤其在大量短生命周期线程场景)
- 节点分配频繁触发
malloc,而 glibc 的malloc在高并发下有锁 - 跳表高层节点(level > 2)被多个线程反复读写,导致 false sharing(即使数据无关,同一 cache line 被不同 CPU 核修改)
优化建议:
- 预分配 node 内存池,用
cds::container::skip_list::traits::node_allocator替换默认分配器 - 限制最大层数(
max_height = 16通常够用),避免高层过度膨胀 - key 类型尽量小且无动态分配(比如用
int64_t而非std::string作 key),减少比较开销和 cache 占用
别忽略“有序”带来的隐含代价:范围查询无法真正无锁
所有无锁跳表都支持单 key 的 insert/get/erase,但 lower_bound、upper_bound 或区间遍历(如 “所有 key ∈ [100, 200]”)本质上需要多节点协同快照,目前没有主流实现提供真正无锁的范围操作。
libcds 提供 find_with 和 for_each,但它们:
- 不保证遍历时的顺序一致性(可能跳过刚插入的项,或重复返回已删除项)
- 不能替代数据库式的 ACID 范围扫描
- 若业务强依赖精确范围结果,要么加读锁(牺牲并发),要么改用分段锁的有序结构(如
folly::Synchronized<:map>)
真正难的不是写一个能跑的无锁跳表,而是判断你的业务是否真的需要它——很多时候,用好 std::shared_mutex + std::map,再配合读多写少的数据分片,反而更稳、更易 debug。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











