无锁队列:Michael-Scott 算法与 ABA 问题
当多线程争抢一把 mutex 保护的队列时,吞吐量随线程数增长反而下降——锁成了瓶颈。无锁队列用 CAS 循环取代互斥,换来真正的无阻塞并发,但也引入了 ABA 问题和内存回收的工程深渊。
发布来自土法炼钢兴趣小组的知识、笔记、进展和应用。主题包括数据结构和算法、编程语言、网络安全、密码学等。
共 8 篇文章 · 返回首页
当多线程争抢一把 mutex 保护的队列时,吞吐量随线程数增长反而下降——锁成了瓶颈。无锁队列用 CAS 循环取代互斥,换来真正的无阻塞并发,但也引入了 ABA 问题和内存回收的工程深渊。
Treiber 栈可能是最简单的无锁数据结构——只需要一个 CAS 操作就能完成 push 和 pop。但'最简单'不意味着'没有坑'。从 ABA 问题到伪共享,从指数退避到 elimination backoff,这个看似朴素的数据结构蕴含了并发编程最核心的设计权衡。
Java 的 `java.util.concurrent` 提供了 ConcurrentHashMap,却没有 ConcurrentTreeMap——取而代之的是一个基于跳表的 ConcurrentSkipListMap。为什么 Doug Lea 选择了跳表而不是红黑树?因为平衡树的旋转操作会同时修改多个节点的指针,在并发场景下几乎不可能做到无锁;而跳表天然的分层链表结构使得每次修改只涉及局部指针,为 CAS 操作提供了完美的施展空间。
在无锁编程的世界里,内存回收是最棘手的难题之一。Rust 的 crossbeam 库用基于纪元的回收机制,巧妙地将这一难题化解为三个整数的优雅舞蹈——本文从原理到工程实践,完整剖析这一精妙的并发内存回收技术。
Java ConcurrentHashMap 从 16 段分段锁演进到 CAS+TreeBin,再到 Cliff Click 的全无锁方案——并发哈希表的二十年进化史,是一部在吞吐量与正确性之间反复拉扯的工程史诗。
在无锁数据结构中,你不能简单地 free() 一块内存——因为另一个线程可能正在读取它。Hazard Pointers 是解决这个问题的经典方案。
系统梳理 io_uring 的线程安全模型与四种主流多线程架构模式(Thread-per-Ring、Shared Ring、Submit/Reap 分离、SQPOLL),包含完整的多线程 Echo Server 实战、NUMA 优化、性能对比与生产避坑指南。
拆解 GitHub 高星'无锁'库的真实面目:隐藏的 mutex、被滥用的 memory_order_relaxed、以及 CAS 重试循环的阻塞本质。附 x86 vs ARM 上的行为差异实测。