同步与互斥
并发共享资源的两个问题:互斥(同时只能一个进程进临界区)、同步(执行顺序有要求)。面试主线:原语是什么、经典问题怎么解。
临界区与原子性
- 临界区:访问共享资源的代码段,同一时刻只允许一个进程进入
- 原子操作:不可分割的操作(如单条指令),是互斥的基础
- 互斥的实现要求:忙等要避免(自旋锁忙等浪费 CPU,短临界区可接受)、不能死锁
互斥原语
| 原语 | 机制 | 适用 |
|---|---|---|
| 互斥锁(Mutex) | 一个线程持有,其他阻塞等待 | 临界区互斥 |
| 自旋锁 | 拿不到就忙等(循环检查) | 临界区极短、多核 |
| 读写锁 | 读共享、写互斥 | 读多写少 |
| 信号量 | 计数器,P/V 操作(down/up) | 互斥 + 资源计数 + 同步 |
信号量是通用原语:值为 1 时就是互斥锁,值为 N 时是资源池(如连接池),还用于同步(一个进程等另一个完成)。
P(wait):计数减 1, 小于 0 则阻塞
V(signal):计数加 1, 唤醒等待者经典同步问题
生产者-消费者: 两个信号量控制空/满
生产者-消费者(高频考点):
empty = N(空位计数), full = 0(已用计数)
生产者: P(empty) → 放入 → V(full)
消费者: P(full) → 取出 → V(empty)要点:P 的顺序不能反(先拿缓冲区锁再等空位会死锁),缓冲区本身还要一把互斥锁。
哲学家进餐:五个人围桌,两叉子才能吃。解法:同时拿起两把叉子(原子)、限制最多四人同时拿、或奇偶编号错开。考点是预防死锁(见死锁篇)。
读者写者:读者可并发,写者独占。偏好问题:读者优先会饿死写者,写者优先反之。读写锁就是答案。
面试追问
- 互斥和同步的区别? 互斥是资源独占(锁),同步是执行顺序(信号量)。信号量都能做
- 信号量怎么实现互斥? 初值 1,P 进入 V 离开。初值 N 是资源池
- 自旋锁什么时候用? 临界区极短(几条指令):忙等的代价小于阻塞唤醒的代价。单核无抢占场景下自旋甚至可能死锁(持锁进程不被调度),要关中断或让出 CPU
- 生产者消费者怎么防止死锁? P 的顺序:先等资源信号量再进临界区。顺序反了会互相持有等待
- 读者写者问题? 读写锁:读共享写互斥。读者优先会饿死写者,要有写者优先或公平策略