Appearance
管程
概念
管程(monitor,也叫"监视器")是一种高级同步机制:把共享变量以及对这些变量的全部操作封装成一个模块,并保证"任一时刻最多只有一个进程在管程内活动"。
管程的四大组成(必背):
| 组成 | 内容 |
|---|---|
| 名称 | 管程自己的标识 |
| 共享数据结构 | 局部于管程的变量(如缓冲区、计数器) |
| 一组过程(函数) | 对这些数据结构的操作(如 put()、get())——外部只能通过这些过程访问数据 |
| 初始化语句 | 给局部共享数据设初值的代码 |
一句话说清它是什么:管程就是"编译器帮你自动加锁的封装模块"——在 os/13-semaphore.md / os/14-classic.md 里,互斥锁要程序员自己 P/V;在管程里,你只要写"过程","进管程加锁、出管程解锁"由语言自动完成。
★ 管程与信号量是"两种并列的同步工具",不是"管程建立在信号量之上"(Hoare 的原始定义里管程是语言机制,实现可以不用信号量)。408 的常考对比就在这一点。
原理
一、管程的三个特征
| 特征 | 含义 | 与"类/对象"的区别 |
|---|---|---|
| 封装性 | 共享数据只能被管程内的过程访问 | 同"类" |
| 互斥性 | 任一时刻只允许一个进程在管程内(由编译器自动加锁,程序员无需写任何 P/V) | 这是"管程"区别于"类"的核心 |
| 条件变量 | 提供 wait / signal 用于"等条件成立" | 类里没有 |
⚠️ 管程不是进程:管程是"被动的资源",进程调用它的过程才进入它——管程没有自己的执行流(这是"管程"与"进程"最容易混的一处概念)。
二、条件变量(本章第一难点)
为什么要条件变量:光有"互斥"不够——生产者在缓冲区满时不能只是"退出管程再重试"(那会疯狂空转),它要"在管程内等某个条件成立"。这个"等"就是条件变量。
c
condition notFull, notEmpty; /* 两个条件变量: 不满 / 不空 */
/* 只能这样用(在管程的过程内部) */
x.wait(); /* ① 释放管程的互斥权 ② 把自己挂到 x 的等待队列上 ③ 阻塞 */
x.signal(); /* 唤醒 x 等待队列上的一个进程(★ 若无人在等, 什么也不做 —— signal 丢失) */⚠️ 条件变量与信号量的本质区别(必考对比):
| 对比项 | 条件变量 wait/signal | 信号量 P/V |
|---|---|---|
| 有没有计数值 | 没有(只是一个等待队列) | 有 value |
无人在等时 signal 会怎样 | 丢失(什么都不发生) | value 加一("记住"这次释放) |
wait 是否一定阻塞 | 一定(无条件挂起) | 不一定(value > 0 时直接通过) |
| 谁保证互斥 | 编译器/语言(进入过程自动加锁) | 程序员(手写 P/V) |
| 能不能"计数" | 不能(要计数就得配一个整型变量自己数) | 能(value 就是计数) |
⚠️ 这就是"条件变量必须配
while而不是if"的根源:被唤醒的进程重新拿到管程时,条件可能已经被别人改回去了(因为它不是在signal的瞬间就接着跑的——见下节 Hoare 与 Mesa)。
三、Hoare 语义与 Mesa 语义(本章第二难点)
signal 之后"谁先跑",有两种主流约定:
| 语义 | signal 之后发生什么 | 被唤醒者何时运行 | 违反的准则 |
|---|---|---|---|
| Hoare 语义("移交") | 调用者立即让出管程并阻塞,被唤醒者立刻接着运行 | 立刻(管程被"移交") | 不违背任何准则(但实现复杂、切换多) |
| Mesa 语义("继续") | 调用者继续执行到自然退出管程,被唤醒者进就绪队列 | 之后重新竞争进入 | ⚠️ signal 后条件可能变化 → 被唤醒者必须用 while 复查 |
Mesa 语义下"必须用 while"的完整理由:
① 生产者 P1 发现缓冲区满 -> 执行 notFull.wait() (睡下)
② 生产者 P2 放入一个产品 -> 执行 notFull.signal() (唤醒 P1, 但 P2 继续跑)
③ 消费者 C1 抢在 P1 之前进入管程 -> 把那个产品取走了 (缓冲区又满了!)
④ P1 终于被调度进入管程 -> 若用 if, 它会以为"不满"直接放入 -> 缓冲区溢出!
若用 while, 它会重新检查 -> 发现还是满 -> 接着睡 ✅⚠️ Java 的
synchronized+wait/notify是 Mesa 语义(notify不释放锁,只把人从条件队列搬到入口等待队列);Python 的threading.Condition也是 Mesa 语义。现实中"Hoare 语义"几乎见不到——所以"必须用while复查"是工程铁律。
四、用管程解决生产者-消费者(与 PV 版逐行对照)
c
monitor ProducerConsumer {
int buffer[N]; /* ★ 共享数据结构: 放在管程里 */
int count = 0, in = 0, out = 0; /* ★ 局部变量, 外部看不见 */
condition notFull, notEmpty; /* ★ 两个条件变量 */
void put(int item) { /* 过程: 进入/退出管程由编译器加解锁 */
while (count == N) notFull.wait(); /* ★ while, 不是 if */
buffer[in] = item;
in = (in + 1) % N;
count++;
notEmpty.signal(); /* ★ 通知"不空了" */
}
int get(void) {
while (count == 0) notEmpty.wait(); /* ★ while */
int item = buffer[out];
out = (out + 1) % N;
count--;
notFull.signal(); /* ★ 通知"不满了" */
return item;
}
} /* 生产者/消费者直接调用 put()/get(), 不写任何 P/V */
/* 生产者 */ while (true) { 生产一个产品 x; ProducerConsumer.put(x); }
/* 消费者 */ while (true) { x = ProducerConsumer.get(); 消费 x; }与 PV 版的逐项对照(这张表是本节的核心得分点):
| 环节 | PV 版(os/14-classic.md) | 管程版 |
|---|---|---|
| 互斥 | 手写 P(mutex) / V(mutex) | 编译器自动加(进入 put/get 时加锁) |
| 缓冲区数据 | 放在管程外面,两个进程各自访问 | 放在管程内部,只能通过过程访问 |
| 等空位 / 等产品 | P(empty) / P(full)(有计数) | notFull.wait() / notEmpty.wait()(无计数) |
| 通知 | V(full) / V(empty)(无人在等也不丢) | notEmpty.signal() / notFull.signal()(无人在等就丢) |
| 条件复检 | 不需要(P 不会"白醒") | 必须 while(Mesa 语义下会"白醒") |
| 程序员出错点 | P/V 漏写、顺序错 → 死锁 | 写不出 P/V 的错;但要小心"用 if 代替 while" |
⚠️ 为什么管程版不需要"同步信号量的初值":empty/full 的计数功能被"count 这个普通变量"代替了——条件变量只负责"等与唤醒",计数是普通变量自己的事。
五、管程与信号量的总对比
| 对比项 | 信号量 | 管程 |
|---|---|---|
| 互斥由谁保证 | 程序员(手写 P/V) | 编译器 / 语言 |
| 是否需要初值 | 需要(互斥 1 / 同步 0 / 计数 = 资源数) | 只需给共享变量设初值 |
| "等"的机制 | value 计数 + 阻塞队列(有记忆) | 条件变量(无记忆,signal 会丢失) |
| 同步代码的位置 | 分散在各进程里 | 集中在管程的过程里 |
| 典型错误 | P/V 数量不配、顺序颠倒、忘写初值 | if 代替 while、signal 当 V 用 |
| 是否满足让权等待 | 是(记录型) | 是 |
| 局限 | 容易写错;调试困难 | 需要语言支持;难以跨机器(分布式)使用 |
| 代表实现 | Linux 内核 struct semaphore、POSIX sem_t | Java synchronized、Python threading.Condition |
示例
例 1:signal 丢失——条件变量与 V 的最大差别
依次执行:P1 执行
x.wait()→ P0 执行x.signal()→ P2 执行x.signal()。条件变量x的等待队列如何变化?
| 步 | 操作 | 条件队列(x) | 说明 |
|---|---|---|---|
| 1 | P1 x.wait() | [P1] | 释放管程互斥权,自己挂到 x 上 |
| 2 | P0 x.signal() | [] | 队列非空 → 唤醒 P1(P1 进就绪队列) |
| 3 | P2 x.signal() | [] | 队列为空 → 这次 signal 被丢弃(丢失) |
⚠️ 关键对照:如果第 3 步换成信号量的 V(s)(初值 0),则 value 会变成 1 —— 这次"释放"被记住了,下一次 P(s) 会直接通过。 条件变量没有这种"记忆"能力。
工程含义:"
signal丢失"不是 bug,而是设计——它逼你用"普通变量 + 条件变量"的组合来表达计数(如管程版里的count)。想省事就容易错,这是管程给程序员留的唯一陷阱。
例 2:Hoare 与 Mesa——"被唤醒者什么时候跑"
P1 在管程内因条件不满足而
wait;P0 在管程内让条件成立,并signal唤醒 P1。两种语义下的时序如下。
Hoare 语义("移交管程"):
时间 →
P0: [进入管程][改条件][signal]──────────(阻塞, 进 P0 的等待队列)
↓ 立刻移交
P1: [被唤醒并当场继续][…][退出管程]
P0: [重新进入管程][退出]
↑ 管程内部任何时刻只有 1 个进程在跑Mesa 语义("继续执行"):
时间 →
P0: [进入管程][改条件][signal][继续执行, 做完自己的事][退出管程]
↓ 只是把人放进就绪队列
P1: (就绪) —— 等 P0 退出并竞争到锁后, 才重新进入
P1: [进入管程][while 复查条件]| 对比项 | Hoare | Mesa(Java / Python) |
|---|---|---|
signal 后调用者 | 立即阻塞并让出管程 | 继续执行到退出 |
| 被唤醒者 | 立刻在管程内运行 | 进就绪队列,重新竞争 |
| 条件是否可能又变了 | 不会(有移交保证) | 可能变了 → 必须 while 复查 |
| 上下文切换次数 | 多(每次 signal 都要切) | 少 |
| 现实中使用 | 少见(教学口径) | 主流(Java、Python、C# 都是) |
⚠️ 答题要点:问"为什么条件变量必须配
while"——答案就是"Mesa 语义下被唤醒者不立刻运行,条件可能被第三个进程改回去"。若题目指定 Hoare 语义,则if也可以(但实际系统一律按 Mesa 处理)。
例 3:管程版生产者-消费者的一次执行
缓冲区大小 3;某时刻缓冲区为空。下面是一次合法执行的 10 步轨迹(
signal时若无人等待则丢弃)。
| 步 | 执行者 | 动作 | 缓冲区 | notFull 队列 | notEmpty 队列 |
|---|---|---|---|---|---|
| 1 | 生产者 | 进入管程(自动加锁) | 0/3 | 空 | 空 |
| 2 | 生产者 | while (count == 3) → 不满,不等待 | 0/3 | 空 | 空 |
| 3 | 生产者 | 放入一个产品(count = 1) | 1/3 | 空 | 空 |
| 4 | 生产者 | notEmpty.signal() | 1/3 | 空 | 无人等 → 丢失 |
| 5 | 生产者 | 退出管程(自动解锁) | 1/3 | 空 | 空 |
| 6 | 消费者 | 进入管程 | 1/3 | 空 | 空 |
| 7 | 消费者 | while (count == 0) → 非空,不等待 | 1/3 | 空 | 空 |
| 8 | 消费者 | 取出一个产品(count = 0) | 0/3 | 空 | 空 |
| 9 | 消费者 | notFull.signal() | 0/3 | 无人等 → 丢失 | 空 |
| 10 | 消费者 | 退出管程 | 0/3 | 空 | 空 |
三条结论:
- 第 4、9 步的
signal都"丢失"了——但系统仍然正确,因为生产者/消费者各自会在while里检查count。这正是"条件变量无记忆"却不妨碍正确性的原因。 - 缓冲区不足/为空时的等待是"睡在条件队列",不是"退出管程重试"——所以管程版不空转。
- 把
while换成if会怎样?在本例(单生产者单消费者)下可能看不出问题,但"唤醒白醒"一旦发生就会缓冲区溢出或取空——例 2 的第 ③ 步给了具体剧本。
例 4:C 实现——管程的"加锁 + 条件队列"骨架模拟
#include <stdio.h>
/* 用三个计数器模拟一个管程: 互斥权、条件队列长度、缓冲区占用
* —— 这是"语义模拟", 不是真实并发 (本机不跑多线程) */
static int mutex_held = 0; /* 0 = 管程空闲, 1 = 有人在内 */
static int q_notFull = 0, q_notEmpty = 0;
static int count = 0;
#define N 3
static int step_no = 0;
static void show(const char *who, const char *what) {
step_no++;
/* 中文标签放最后: printf 对中文按"字节"补齐, 放前面必然错位 */
printf("%2d. 缓冲=%d/%d notFull队列=%d notEmpty队列=%d | %s %s\n",
step_no, count, N, q_notFull, q_notEmpty, who, what);
}
int main(void) {
printf("管程版生产者-消费者: 缓冲区 %d, 初始为空\n", N);
mutex_held = 1; show("生产者", "进入管程(自动加锁)");
show("生产者", "while(count==3) -> 未满, 不等待");
count = 1; show("生产者", "放入产品, count=1");
if (q_notEmpty == 0) show("生产者", "signal(notEmpty) 无人等 -> 丢失");
else { q_notEmpty--; show("生产者", "signal(notEmpty) 唤醒一个"); }
mutex_held = 0; show("生产者", "退出管程(自动解锁)");
mutex_held = 1; show("消费者", "进入管程(自动加锁)");
show("消费者", "while(count==0) -> 非空, 不等待");
count = 0; show("消费者", "取出产品, count=0");
if (q_notFull == 0) show("消费者", "signal(notFull) 无人等 -> 丢失");
else { q_notFull--; show("消费者", "signal(notFull) 唤醒一个"); }
mutex_held = 0; show("消费者", "退出管程(自动解锁)");
printf("管程空闲? %s ; 缓冲区 %d/%d\n", mutex_held ? "否" : "是", count, N);
return 0;
}
c 本站为静态站,不提供在线运行;可复制到本地用 gcc / python 执行
预期输出:
管程版生产者-消费者: 缓冲区 3, 初始为空
1. 缓冲=0/3 notFull队列=0 notEmpty队列=0 | 生产者 进入管程(自动加锁)
2. 缓冲=0/3 notFull队列=0 notEmpty队列=0 | 生产者 while(count==3) -> 未满, 不等待
3. 缓冲=1/3 notFull队列=0 notEmpty队列=0 | 生产者 放入产品, count=1
4. 缓冲=1/3 notFull队列=0 notEmpty队列=0 | 生产者 signal(notEmpty) 无人等 -> 丢失
5. 缓冲=1/3 notFull队列=0 notEmpty队列=0 | 生产者 退出管程(自动解锁)
6. 缓冲=1/3 notFull队列=0 notEmpty队列=0 | 消费者 进入管程(自动加锁)
7. 缓冲=1/3 notFull队列=0 notEmpty队列=0 | 消费者 while(count==0) -> 非空, 不等待
8. 缓冲=0/3 notFull队列=0 notEmpty队列=0 | 消费者 取出产品, count=0
9. 缓冲=0/3 notFull队列=0 notEmpty队列=0 | 消费者 signal(notFull) 无人等 -> 丢失
10. 缓冲=0/3 notFull队列=0 notEmpty队列=0 | 消费者 退出管程(自动解锁)
管程空闲? 是 ; 缓冲区 0/3⚠️ 三点说明:
- 中文一律放在格式串最后(前面的列只有数字与 ASCII,任何终端下都对齐)——
printf的宽度说明符对中文按"字节"填充,%-24s遇到长度不一的汉字标签必然错位(os/10-process.md例 3 已踩过这个坑)。 mutex_held是"管程互斥权"的替身:进入过程时置 1、退出时置 0,由编译器生成,不是程序员写的——这就是管程与信号量最大的分工差异。- 本程序是"单线程把一次合法执行演一遍",它没有真的并发;下例用真实线程库来验证互斥确实成立。
例 5:Python——用真实线程库跑管程版生产者-消费者
import threading
import collections
BUF = collections.deque() # 共享数据结构 (相当于管程里的 buffer)
MAXB = 3
cv = threading.Condition() # ★ 管程 = 互斥锁 + 条件变量 (Python 是 Mesa 语义)
produced, consumed = [], []
maxuse = [0] # 缓冲区峰值占用
inbuf = [0] # 正在操作缓冲区的线程数
maxbuf = [0] # 其峰值 (必须为 1)
inmon = [0] # 停留在管程代码区间内的线程数
maxin = [0]
def producer():
for i in range(10):
with cv: # ★ 进入管程: 自动加锁
inmon[0] += 1; maxin[0] = max(maxin[0], inmon[0])
while len(BUF) >= MAXB: # ★ while 而不是 if (Mesa 语义)
cv.wait() # 释放管程并挂到条件队列
inbuf[0] += 1; maxbuf[0] = max(maxbuf[0], inbuf[0])
BUF.append(i); produced.append(i)
maxuse[0] = max(maxuse[0], len(BUF))
inbuf[0] -= 1
inmon[0] -= 1
cv.notify_all() # signal: 唤醒等待者
def consumer():
for i in range(10):
with cv:
inmon[0] += 1; maxin[0] = max(maxin[0], inmon[0])
while not BUF:
cv.wait()
inbuf[0] += 1; maxbuf[0] = max(maxbuf[0], inbuf[0])
consumed.append(BUF.popleft())
inbuf[0] -= 1
inmon[0] -= 1
cv.notify_all()
t1 = threading.Thread(target=producer, daemon=True)
t2 = threading.Thread(target=consumer, daemon=True)
t1.start(); t2.start(); t1.join(); t2.join()
print('生产序列 =', produced)
print('消费序列 =', consumed)
print('缓冲区峰值占用 = %d (上限 %d)' % (maxuse[0], MAXB))
print('同时停留在管程代码区间的线程数峰值 = %d (= 1 个在跑 + 1 个挂在条件队列)' % maxin[0])
print('同时操作缓冲区的线程数峰值 = %d (必须为 1 -> 互斥成立)' % maxbuf[0])
print('一致性: 生产 10 = 消费 10 且顺序一致 ->', produced == consumed == list(range(10)))
print('')
print('=== 条件变量 signal 在无人等待时丢失 ===')
q = []
for who, op in [('P1', 'wait'), ('P0', 'signal'), ('P2', 'signal')]:
if op == 'wait':
q.append(who)
print(' %s wait -> 释放管程, 进条件队列 %s' % (who, q))
elif q:
print(' %s signal -> 唤醒 %s, 队列 %s' % (who, q.pop(0), q))
else:
print(' %s signal -> 队列为空 -> signal 被丢弃(丢失)' % who)
print('')
print('=== Hoare 与 Mesa 语义对照 ===')
for r in [('Hoare', '调用者立即让出管程并阻塞', '被唤醒者立刻接着运行', '不需要 while 复查'),
('Mesa', '调用者继续执行到自然退出', '被唤醒者进就绪队列重新竞争', '必须用 while 复查')]:
print(' %-6s %-22s %-22s %s' % r)
python 本站为静态站,不提供在线运行;可复制到本地用 gcc / python 执行
输出对照(真实运行结果):
生产序列 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
消费序列 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
缓冲区峰值占用 = 3 (上限 3)
同时停留在管程代码区间的线程数峰值 = 2 (= 1 个在跑 + 1 个挂在条件队列)
同时操作缓冲区的线程数峰值 = 1 (必须为 1 -> 互斥成立)
一致性: 生产 10 = 消费 10 且顺序一致 -> True四条结论:
同时操作缓冲区的线程数峰值 = 1——这是"管程互斥"的直接证据:任何时刻只有一个线程在动缓冲区,而这个锁不是我们写的,是with cv:自动加的。- "停留在管程代码区间的线程数峰值 = 2":一个在跑、一个挂在条件队列上等——注意这不违背互斥:
cv.wait()已经把管程互斥权释放了,"在管程里等"和"在管程里跑"是两回事。 - 生产/消费序列都是
0..9且顺序一致——单生产者单消费者的 FIFO 语义成立(缓冲区峰值恰好等于上限 3 也说明"满了就等"生效了)。 while是必须的:Python 的Condition是 Mesa 语义(notify_all不释放锁),被唤醒的线程要重新抢锁并复查条件——换成if,在"多生产者/多消费者"下就会出问题。
考点
考点
1. 必背结论
- 管程四大组成:名称、共享数据结构、一组过程、初始化语句。
- 管程三大特征:封装性、互斥性(由编译器自动保证)、条件变量。
- 管程不是进程:它是被动对象,进程调用它的过程才进入它。
- 条件变量无计数值;
signal在无人在等时会被丢弃(丢失),而V会把value加一(有记忆)。 wait一定阻塞(释放管程互斥权);P在value > 0时直接通过。- Hoare 语义:
signal后调用者阻塞、被唤醒者立刻运行(管程"移交");Mesa 语义:signal后调用者继续执行到退出、被唤醒者重新竞争进入。 - Mesa 语义下必须用
while复查条件(Java、Python 都是 Mesa)。 - 管程版生产者-消费者:
while (count == N) notFull.wait()+notEmpty.signal();互斥由编译器加,不需要mutex信号量。 - "同步工具"两大类:信号量(程序员写 P/V)与管程(编译器保证互斥);管程难以用于分布式系统(需要集中式互斥权)。
2. 高频陷阱
- 把
signal当V用(以为它会"记住"):错。无人在等时signal直接丢失——要计数就必须配一个普通整型变量。 - 用
if代替while检查条件:错(Mesa 语义下被唤醒者可能"白醒",条件已被第三方改回)。 - 说"管程里的互斥要靠程序员写 P/V":错。管程的互斥由编译器/语言自动加,程序员写的只是过程体。
- 说"wait 只是把进程挂起":不完整。
wait的第一件事是"释放管程的互斥权"——否则别人根本进不来唤醒你(死锁)。 - 把"管程"当成"一个进程"或"一个线程":错。管程是被动资源,没有自己的执行流。
- 认为"管程可以随便替代信号量":错。管程需要语言支持,且难以扩展到多机(分布式)——这是它与信号量的重要分界。
- 忘了"条件变量与信号量不是一回事"就乱套用:条件变量只解决"等与唤醒",计数靠普通变量;信号量的
value本身就是计数器。 - 说"Hoare 语义更好所以现实中都用它":错,现实中(Java/Python/C#)清一色 Mesa 语义;Hoare 只是教材口径。
- 把"进入管程排队"与"在条件变量上等待"混为一谈:前者是等互斥权(入口队列),后者是等条件成立(条件队列)——两个队列位置不同、唤醒方式不同。
3. 解题模板("管程题")
① 判断题型: 是"写管程代码"还是"概念对比"?
② 写代码:
monitor 名字 { 共享变量; condition 变量...; void 过程(){ while(条件不满足) c.wait(); ... c.signal(); } }
互斥不用写 P/V; 但条件必须 while; 计数必须建普通变量
③ 概念题就答三张表:
- 管程四大组成 / 三大特征
- 条件变量 vs 信号量 (记忆性、是否一定阻塞、谁保证互斥)
- Hoare vs Mesa (谁先跑、要不要 while)
④ 与 PV 版对照时, 强调"互斥的来源变了"(手写 -> 编译器), 而"等着条件成立"换成"条件变量"4. 与相邻章节的接口
os/13-semaphore.md(信号量):管程是"并列的另一套工具"——value会记住、signal不会,这是两章最该分清的一处。os/14-classic.md(经典问题):管程版生产者-消费者是 PV 版的"瘦身版"(少了mutex与empty/full三个信号量,换成一个count+ 两个条件变量)。os/12-sync.md(同步与互斥):管程的互斥性是"由语言保证的互斥"——它替代了那章全部软件/硬件方法的职责。os/16-deadlock.md(死锁):管程里同样会死锁(如两个条件变量互相等待、wait前忘了让条件成立)——四条必要条件在管程里一样成立。os/10-process.md(进程与线程):例 5 的"管程代码区间线程数峰值 = 2"正是"一个在跑、一个在等"——wait阻塞后线程状态从运行变阻塞。
小结
- 管程 = 封装共享数据 + 一组过程 + 自动互斥 + 条件变量;互斥由编译器保证,程序员不再写 P/V。
- 条件变量两大要点:无计数值(
signal无人在等就丢失)、wait一定阻塞且会先释放管程互斥权。 V有记忆、signal无记忆——这是信号量与管程最本质的区别(例 1 三行就演示完了)。- Hoare vs Mesa:Hoare 移交管程(被唤醒者立刻跑,不需要
while);Mesa 继续执行(被唤醒者重新竞争,必须while)——现实中全是 Mesa。 - 管程版生产者-消费者:
while (count == N) notFull.wait(); ... notEmpty.signal();——没有mutex、没有empty/full,因为计数交给了普通变量count。 - 例 5 的真实线程验证:缓冲区操作并发峰值 = 1(互斥成立)、生产/消费序列都是
0..9(FIFO 正确)、缓冲区峰值 = 3(满就等)。
评论(0)
当前浏览器不允许本地存储,评论无法保存。
还没有评论,来说两句。