---
一句话回答
好用,但有严格的适用范围。用对了是利器,用错了是灾难。
它既不是银弹,也不是屠龙术。下面直接从实战角度拆开说。
---
一个真实案例
某推理网关,几十个 worker 线程抢一个任务队列。火焰图上 llllockwait 常年霸榜前三。换成无锁 Treiber 栈,QPS 涨了四成,上线后在 x86 集群跑了整整一年,一次事故都没有。[citation:87.16]
另一个案例:百万级 QPS 的金融交易系统,加锁方案下锁争抢消耗了 40% 以上的 CPU。团队花了三周做无锁改造才把吞吐拉上去。[citation:87.4]
但同一批人后来在 ARM 平台部署同样的无锁代码,出了诡异的数据错乱——内存模型不一样,x86 上跑得好的逻辑在 ARM 上不成立。[citation:87.16]
---
什么时候推荐用
| 场景 | 理由 | 典型例子 |
|:----|:------|:---------|
| 高争抢的热点路径 | 锁等待占总 CPU >20% | 消息队列、任务分发、计数器 |
| 单写多读 | 天然无需互斥 | 配置更新、状态发布 |
| 极短操作(纳秒级) | CAS 比锁上下文切换快几个数量级 | 引用计数、指针替换 |
| 不能阻塞的上下文 | 中断/信号处理函数里不能休眠 | 内核路径、实时系统 |
| 核心基础设施 | 被千万次调用的底层库 | 无锁队列、内存分配器 |
---
什么时候不要碰
| 场景 | 原因 |
|:----|:------|
| 普通业务逻辑 | 用锁足够,无锁的调试成本远超收益 |
| 复杂数据结构 | 无锁红黑树?写出来也没人敢 review |
| 跨平台部署 | x86 强内存模型掩盖了问题,ARM/PowerPC 上暴雷 |
| 团队没有并发专家 | 无锁 bug 极其隐蔽,跑几个月才出现一次 |
| 没有 perf 数据支撑 | "觉得锁慢"不是理由,先 profile 确认 |
---
四大陷阱(每一个都值一个线上事故)
| 陷阱 | 现象 | 本质 |
|:----|:-----|:------|
| ABA 问题 | CAS 读到 A→改 B→改回 A,你以为没变过 | 需要加版本号解决 |
| 伪共享(False Sharing) | 性能不升反降,甚至比加锁还慢 | 不同线程的变量碰巧在同一条 CacheLine 上 |
| 半初始化发布 | 拿到指针但对象构造到一半 | 缺少 acquire/release 屏障 |
| 单边屏障失效 | x86 正常,ARM 上数据错乱 | 只对自己的变量加屏障,忽略了对面线程的视角[citation:87.16] |
---
实战建议
① 优先用造好的轮子
不需要自己手写无锁数据结构——标准库里基本都有现成的:
| 语言 | 直接可用的无锁容器 |
|:----|:------------------|
| C++ | std::atomic<>, std::atomic_flag, boost::lockfree::queue/stack |
| C# | ConcurrentQueue, ConcurrentDictionary, Interlocked |
| Go | atomic 包,channel 本身就是无锁队列的封装 |
| Java | AtomicInteger, ConcurrentLinkedQueue, LongAdder |
② 先测量,再动手
Linux 上看锁争抢
perf top -p <pid> # 看看 llllockwait 占比
perf record -e sched:sched_switch -p <pid>
如果锁争抢不到 10% 的总 CPU,优化锁不如优化别处。
③ 从简单的开始
推荐路径:
SPSC(单生产者单消费者)队列 → MPSC → MPMC
SPSC 队列实现极其简单(一个 head 指针 + 一个 tail 指针),没有 CAS 争抢,性能极高,而且不需要解决 ABA 问题。大部分"需要一个高性能无锁队列"的场景,SPSC 就够用。
④ 复杂的无锁算法要做好"回退"计划
无锁内存池、无锁哈希表这类重型武器,建议设计时保留退化到加锁路径的能力——线上出诡异常规手段查不出来时,一把切回加锁保命,回头慢慢排查。
---
性能对比(参考值)
| 同步机制 | 延迟(典型) | 备注 |
|:--------|:----------:|:-----|
| CAS(无争抢) | ~10ns | 硬件指令,极快 |
| CAS(有争抢) | ~100ns–1μs | 自旋等待 |
| Mutex(无争抢) | ~25ns | 快速路径 |
| Mutex(有争抢) | ~1–10μs | 上下文切换 |
| 系统调用锁(futex) | ~1–5μs | 进入内核态 |
从有锁换成无锁,在不争抢时可能只快一倍;但在高争抢下差距可以是 两个数量级(锁让 CPU 空转在上下文切换上,CAS 让 CPU 空转在自旋上,后者比前者轻得多)。
---
六、总结
| | 加锁 | 无锁 |
|:--|:-----|:------|
| 开发难度 | 低 | 极高 |
| 调试难度 | 中等 | 地狱级 |
| 高争抢性能 | 差(上下文切换) | 好(自旋) |
| 低争抢性能 | 好 | 好 |
| 可移植性 | 好 | 差(内存模型依赖) |
| 正确性证明 | 人能 review | 需要形式化验证 |
| 适合团队 | 所有团队 | 有并发专家 |
问你一个问题:如果你的无锁代码上线后每三个月崩一次,需要一个小团队花一周才能定位,你还会用它吗? 决定是否用无锁,技术判断只占一半,另一半是对你自己和团队的了解。
0 评论