多线程并发访问共享数据产生竞态(结果取决于执行顺序);同步就是约束并发。
临界区:访问共享资源的代码段,须满足:
实现演进:软件法(Peterson,现代 CPU 乱序下已不可靠)→ 硬件法(关中断、TestAndSet/Swap 原子指令)→ 信号量/锁(封装硬件原语)。
信号量 = 整型变量 + 原子操作:
s--,若结果 < 0 则阻塞自己。s++,若结果 ≤ 0 则唤醒一个等待者。sem_t mutex = 1; // 互斥信号量,初值 1
P(&mutex); // 进入临界区前
/* 临界区 */
V(&mutex); // 离开临界区后
初值 1 = 互斥锁;初值 N(>1)= 计数/资源管理(N 个空缓冲区)。
empty(初值 N)+ full(初值 0)+ 互斥 mutex。P 顺序关键:先 P(empty) 再 P(mutex),否则死锁。生产者-消费者典型实现(注意 P 顺序):
// 生产者:先 P(empty) 再 P(mutex),顺序不能反
P(&empty); P(&mutex);
put_item();
V(&mutex); V(&full);
// 消费者:先 P(full) 再 P(mutex)
P(&full); P(&mutex);
get_item();
V(&mutex); V(&empty);
| 互斥锁(mutex) | 信号量(semaphore) | |
|---|---|---|
| 本质 | 二元信号量 | 计数信号量 |
| 用途 | 仅互斥 | 互斥 + 资源计数 + 同步顺序 |
| 所有权 | 谁加锁谁解锁(有 owner) | 无所有权,任意线程可 V |
| 递归 | 不可递归加锁 | 可多次 P |
| 优先级 | 有优先级继承防优先级反转 | 通常无 |
忙等待(自旋锁)P 失败后循环
while (flag);不放弃 CPU;阻塞则挂起让出 CPU。自旋锁不切上下文,适合临界区极短;阻塞适合临界区长(I/O),代价是一次上下文切换。