Appearance
条件变量
考情分析
管程和条件变量在 408 真题中偶有涉及,属于 🔥 中低频考点,但近年考查频率有上升趋势。
信号量 PV 操作写反就死锁、漏写就数据错乱——这种手动挡太容易翻车了。管程的思路是把共享数据和操作封装在一起,由编译器自动保证互斥,程序员只需要关心同步逻辑。
比信号量更安全的同步机制
信号量机制虽然强大,但编程容易出错:P/V 操作顺序写反会导致死锁,漏写会导致互斥失效。管程(Monitor)的目标是提供一种更高级、更安全的同步机制。
管程的定义
管程是一种封装了共享数据及其操作的同步机制,由以下部分组成:
- 共享数据结构(管程内部的变量)
- 一组过程(对共享数据的操作函数)
- 初始化语句
- 管程名称
monitor ProducerConsumer {
// 共享数据
int buffer[N];
int count = 0;
// 条件变量
condition notFull, notEmpty;
// 过程
procedure insert(item) { ... }
procedure remove() { ... }
}管程的关键特性
| 特性 | 说明 |
|---|---|
| 互斥访问 | 同一时刻最多只有一个进程在管程内执行(编译器自动保证) |
| 封装性 | 共享数据只能通过管程提供的过程访问 |
| 条件等待 | 通过条件变量实现进程的等待和唤醒 |
条件变量
条件变量(Condition Variable)是管程中用于进程等待/唤醒的机制。每个条件变量对应一个等待队列。
两个操作:
| 操作 | 说明 |
|---|---|
wait(c) | 当前进程在条件 c 上阻塞,释放管程的使用权 |
signal(c) | 唤醒在条件 c 上等待的一个进程 |
条件变量 vs 信号量
条件变量的 wait 和 signal 与信号量的 P/V 不同:
- 条件变量没有「值」的概念,
signal时如果没有等待进程,该信号就丢失了 - 信号量的 V 操作无论是否有等待进程,都会使 value+1(信号被记录)
用管程解决生产者-消费者问题
monitor ProducerConsumer {
int buffer[N];
int count = 0;
condition notFull, notEmpty;
procedure insert(item) {
if (count == N)
wait(notFull); // 缓冲区满,等待
buffer[count] = item;
count++;
signal(notEmpty); // 通知消费者
}
procedure remove() {
if (count == 0)
wait(notEmpty); // 缓冲区空,等待
item = buffer[count - 1];
count--;
signal(notFull); // 通知生产者
return item;
}
}
// 生产者进程
producer() {
while (true) {
item = produce();
ProducerConsumer.insert(item);
}
}
// 消费者进程
consumer() {
while (true) {
item = ProducerConsumer.remove();
consume(item);
}
}对比信号量方案,管程方案不需要手动 P/V,互斥由管程自动保证,更不容易出错。
Hoare 管程 vs Mesa 管程
当一个进程执行 signal 唤醒另一个进程时,两个进程都想在管程内执行,怎么处理?
| 类型 | 处理方式 | 特点 |
|---|---|---|
| Hoare 管程 | signal 后立即让出管程,被唤醒进程继续执行 | 条件保证成立,但切换频繁 |
| Mesa 管程 | signal 后继续执行,被唤醒进程稍后竞争进入 | 被唤醒时条件可能不满足,需用 while 循环重检 |
Mesa 管程中需要将 if 改为 while:
// Mesa 管程
procedure insert(item) {
while (count == N) // 用 while 而非 if
wait(notFull);
...
}考研高频考点
- 🔥🔥 管程的定义和特性(封装+互斥+条件变量)
- 🔥🔥 条件变量与信号量的区别(有无值的概念)
- 🔥 管程中同一时刻只允许一个进程执行
- 🔥 Hoare 管程 vs Mesa 管程的区别
有了信号量和管程这些同步工具,接下来用它们解决经典问题。下一篇从最核心的生产者-消费者问题开始。