第1章 并发控制概述
并发控制这玩意儿,说白了就是管好"多件事同时发生"的场景。你想想看,一个系统里多个线程、多个进程、甚至多台机器同时在干活,不出乱子才怪。我做了十几年分布式系统,见过太多因为并发没处理好而翻车的案例。
这一章我们先搭个框架。把并发场景、常见问题、控制目标、还有那些策略工具都过一遍。后面的章节再一个一个深入拆解。
1.1 并发场景——哪些地方需要操心
并发不是理论概念,它就藏在我们每天写的代码里。我个人习惯把并发场景分为三类:
- 多线程编程:一个进程内多个线程共享堆内存。Java的Thread、Go的goroutine都算。最常见的战场。
- 多进程协作:进程间通过共享文件、数据库、消息队列等方式交互。操作系统里的经典问题——生产者消费者,就是典型。
- 分布式系统:多台机器通过网络通信,没有真正的共享内存。这儿的并发问题最难搞,因为"时间"和"顺序"都变得模糊了。
具体到业务场景,秒杀抢购、银行转账、库存扣减、分布式ID生成……这些你肯定不陌生。每一个场景背后都藏着并发的影子。
1.2 并发问题——四个最常见的坑
并发场景一旦失控,就会冒出各种各样的毛病。归纳下来,主要有四类。我按危害程度排了个序:
1.2.1 竞态条件
两个线程同时读写一个共享变量,最终结果取决于谁先谁后——这就是竞态。我在项目中遇到过最典型的案例:一个计数器同时在多个goroutine里递增,最后结果总比预期少。
看个例子:
package main
import (
"fmt"
"sync"
)
func main() {
var count = 0
var wg sync.WaitGroup
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
count++ // 这里就是竞态条件
wg.Done()
}()
}
wg.Wait()
fmt.Println(count) // 几乎不会是1000
}
count++ 看着像一行操作,实际拆成了"读-改-写"三步。两个线程同时读到旧值,加完写回去,就覆盖了对方的更新。结果永远少。
1.2.2 死锁
两个线程各持有一把锁,都在等对方释放——谁也动不了。我曾经排查过一个线上服务卡死的问题,查了半天发现是A线程持有锁1等锁2,B线程持有锁2等锁1。经典的"循环等待"。
1.2.3 活锁
活锁比死锁更"温柔"——线程没阻塞,但一直在做无用功。比如两个线程检测到冲突后各自退让,结果又同时重试,反复横跳,就是干不了正事。嗯,这场景在分布式协调里挺常见的。
1.2.4 饥饿
优先级低的线程一直拿不到资源,永远无法执行。我见过一个调度系统,高优任务源源不断,低优任务等了几个小时才被调度——这其实就是饥饿。
这四个问题,我把它们的核心差异整理成了一张表:
| 问题类型 | 直接表现 | 资源状态 | 系统是否可恢复 | 典型触发条件 |
|---|---|---|---|---|
| 竞态条件 | 数据不一致 | 资源正常 | 可恢复(结果已错) | 未加锁的共享写操作 |
| 死锁 | 线程完全卡住 | 锁被永久占用 | 需外部干预 | 循环等待锁 |
| 活锁 | 线程空转 | 资源来回切换 | 可能自行恢复 | 冲突重试策略不当 |
| 饥饿 | 部分线程无进展 | 资源被抢占 | 调整优先级后可恢复 | 不公平的调度策略 |
1.3 并发控制目标——我们要解决什么问题
搞清楚了问题,再看目标就清晰了。并发控制不是"为了控制而控制",它有明确的指向:
- 数据一致性:无论多少线程同时操作,最终数据是正确、完整的。这是底线。
- 系统可用性:并发不能把系统搞崩。死锁、饥饿都会导致服务不可用,必须避免。
- 性能与公平:控制是有代价的。锁加多了,性能就掉了。我们要在"安全"和"快"之间找平衡,同时保证各线程有公平的机会。
这三个目标其实是互相制约的。一致性要求越严,性能损失越大;可用性要求高,可能就得放松一致性。你在做架构决策时,心里得有这个权衡。
1.4 常用策略总览——工具箱里有什么
针对上面那些问题,业界积累了一系列策略。我把它们分成四大类,也是本课程后续要逐一展开的内容:
- 锁机制:互斥锁、读写锁、乐观锁、分布式锁。最经典也最直接的手段。
- 原子操作:CAS、原子变量、无锁数据结构。比锁更轻量,但适用场景有限。
- 事务机制:本地事务、分布式事务(2PC、TCC、Saga)。适合数据库层面的并发控制。
- 限流降级:令牌桶、漏桶、熔断器。重在"保护"而非"协调",防止系统被突发流量冲垮。
下面这张图把整个知识体系串了起来——从场景到问题,从目标到策略,你一看就明白各模块之间的关系。
看到没?从上到下是一条清晰的链路:哪些场景会出问题 → 具体出什么问题 → 我们要达成什么目标 → 用什么工具来解决。后续每一章,就是沿着这条链路逐个击破。
这一章算是"总览"。接下来的章节,我们会从竞态条件开始,一步步深入每个问题、每种策略的细节。你先把这张图刻在脑子里,后面学起来就会轻松很多。