Q59: 如何减少锁竞争?
核心结论
减少锁竞争的核心,不是把锁从代码里删掉,而是减少共享状态和临界区长度。
真正有效的思路通常是:
- 让更多数据只被一个线程拥有
- 缩短持锁时间
- 减少高频全局结构
- 把竞争从“抢同一把锁”变成“分片或串行归属”
无锁结构只是选项之一,不是默认答案。
一、锁竞争真正会带来什么
常见代价包括:
- 线程阻塞
- 上下文切换
- cache line 抖动
- 尾延迟变差
所以锁竞争的问题不只是“CPU 高”,还常常表现为系统不稳定、偶发卡顿和吞吐骤降。
二、先找出竞争来自哪里
高竞争锁通常集中在这些位置:
- 全局 map
- 消息队列
- 定时器管理器
- 日志系统
- 对象池或连接池
所以第一步不是改并发模型,而是先找出哪些数据结构真的在被争抢。
三、最有效的办法通常是减少共享
1. 单线程拥有
让某类对象始终由固定线程或 Actor 拥有,其他线程通过消息交互。
这通常比给对象加很多锁更稳。
2. 分片
把一把全局锁拆成:
- 按 key 分片
- 按房间分片
- 按玩家或场景分区
这样竞争会明显下降。
3. 读写分离或快照化
某些读多写少结构,更适合用:
- 只读快照
- copy-on-write
- 版本化数据
而不是所有访问都抢同一把 mutex。
四、缩短临界区比换锁类型更重要
很多锁竞争并不是锁本身的问题,而是持锁范围太大。
常见坏味道包括:
- 持锁期间做 I/O
- 持锁期间做复杂计算
- 持锁期间调用其他模块
这会把本来可控的锁变成热点瓶颈。
五、无锁不是银弹
无锁结构能减少一部分阻塞,但也会带来:
- 代码复杂度上升
- 调试困难
- 内存序问题
- ABA 等并发风险
只有在热点明确、收益明确时才值得上。
六、日志、定时器、队列最容易成为隐形热点
很多系统主逻辑看起来没问题,但公共设施层会变成全局锁黑洞。
例如:
- 全局日志写锁
- 单一优先队列定时器
- 中央对象池
这类组件应该优先考虑:
- 分线程缓冲
- 批量合并
- 分片结构
七、工程上更稳妥的优化顺序
常见做法是:
- 先通过 profiling 和 tracing 找热点锁
- 先缩小临界区
- 再减少共享和做分片
- 最后再考虑更激进的无锁结构
这通常比一上来重写成 lock-free 更稳。
八、常见误区
1. 锁竞争高就说明锁不能用
不对。很多时候是共享设计有问题,不是锁这种原语本身有问题。
2. 读写锁一定比互斥锁好
不一定。写多、读临界区短时,读写锁可能更差。
3. 无锁一定性能更高
不对。要看争用模式、数据结构和实现质量。
参考资料
- 并发 profiling、Actor 化和分片锁实践资料
