让进程一边算一边等,CPU 就有大半时间在空转:单进程利用率只有一半多;加两个同样爱等的进程,利用率直接顶到九成以上。这就是多道程序设计的全部收益。
4.2 把模拟器跑通了,这一节做第一个真实验:让进程带上 IO 等待,看 CPU 什么时候闲、闲多久。
一、实验设置:一个爱等的进程
进程 P1 要跑 12 拍,但每跑 3 拍就要等 4 拍 IO。 这个比例很贴近真实:一个程序读文件、算一会儿、再读——算与等交替,而等往往比算长。 观察窗口取 20 拍,够看清两个完整周期。
二、看到了什么:一半时间在等
时间线是【跑 3 拍、等 4 拍】的循环。等的那 4 拍里,CPU 上没有别人,只能空转。 实测利用率五成多一点——不是 100%,也不是 0。这个数很重要:它说明【单进程系统里,CPU 的天花板由 IO 比例决定】。 要记住这条:你觉得机器【很慢】,可能不是 CPU 不够,是它在等,而没人在它等的时候干活。
三、加人:三个同样爱等的进程
再放两个同样的进程进去。活儿多了两倍,但总耗时没到三倍。 因为 A 在等 IO 的时候,B 或 C 正好在跑——空转被填上了。 实测利用率从五成多顶到九成以上。这就是【多道程序设计】的全部收益:不是让单件事变快,是让机器不闲着。 注意措辞:单件事的周转时间反而可能变长(要跟别人抢),但整机吞吐量上去了。这正是吞吐与延迟打架的第一个例子。
四、这件事在现实里长什么样
一台机器上跑着几百个进程,绝大多数时候只有几个在真跑,其余都在等 IO、等锁、等定时器。
所以【进程数很多】不等于【机器很忙】——ps aux 里一堆 S 状态,说的是它们都在等。
反过来:一台机器 CPU 占用一直 100%,说明瓶颈在 CPU;占用很低但响应很慢,说明瓶颈在 IO 或锁。
这条判断法的用处:出问题时先看 CPU 占用率,就知道该往哪个方向查。
五、动手 Demo:做一次实验——让进程一边算一边等
先跑一个进程(每 3 拍算、4 拍等),看时间线上那些【等 IO】的格子;再放三个进去,看同样的格子有没有被填上。
# ① 一个进程:每跑 3 拍,等 4 拍
ps = [Proc("P1", 12, io_each=3, io_len=4)]
done, sw, tl, total = run(ps, quantum=4, ncpu=1)
# 时间线里 names[0] == "-" 的那些拍,就是 CPU 空转
# ② 三个同样的进程
ps = [Proc("P1", 12, io_each=3, io_len=4),
Proc("P2", 12, io_each=3, io_len=4),
Proc("P3", 12, io_each=3, io_len=4)]
done, sw, tl, total = run(ps, quantum=2, ncpu=1)
busy = sum(p.ran for p in ps) # 真正占 CPU 的拍数
实跑输出:
--------------------------------------------------------------------
做一次实验——让进程一边算一边等
--------------------------------------------------------------------
① 只有一个进程,但它每跑 3 拍就要等 4 拍
tick 它在干什么
------------------------------
0 在跑
1 在跑
2 在跑
3 等 IO(CPU 闲着)
4 等 IO(CPU 闲着)
5 等 IO(CPU 闲着)
6 在跑
7 在跑
8 在跑
9 等 IO(CPU 闲着)
10 等 IO(CPU 闲着)
11 等 IO(CPU 闲着)
12 在跑
13 在跑
14 在跑
15 等 IO(CPU 闲着)
16 等 IO(CPU 闲着)
17 等 IO(CPU 闲着)
18 在跑
19 在跑
总耗时 21 拍
它真正占 CPU 12 拍
CPU 利用率 57%
**一个进程一边算一边等,CPU 就有大半时间在空转。**
**这就是为什么要有【多道】——让别的进程在它等的时候顶上。**
② 加两个同样爱等的进程,再看利用率
进程数 总耗时 占 CPU 拍数 利用率
---------------------------------------------
1 个 21 12 57%
3 个 37 36 97%
**活儿多了三倍,但总耗时没到三倍**——因为等待期被别人的计算填上了。
**这就是【多道程序设计】的全部收益:不是让单件事变快,是让机器不闲着。**
单进程那张时间线上一半是空的——这不是模型的问题,是现实:一个只会算和等的进程,就是让 CPU 一半时间闲着。 三个进程时,总耗时 37 拍左右,而合计占 CPU 36 拍——利用率九成七。几乎每一拍都有人在跑。 注意【三倍活儿没换来三倍时间】:如果单进程是 21 拍,三个应该是 63 拍;实际只要 37 拍——省下来的正是原来空转的那些拍。
📋 在真 Linux 上复核:cat /proc/stat 第一行 cpu 后面的数字是各状态累计时间(user/nice/system/idle/iowait…),两次数值相减除以总增量就是这段时间的利用率;vmstat 1 会每秒打一行,看 id 与 wa 两列。
30 秒实验
- 把
io_len从 4 改成 1,看利用率变成多少(等得少了,空转就少)。 - 把进程数加到 8 个,看利用率能不能到 100%——到不了,因为总有一段时间全在等。
vmstat 1 5看id(空闲)与wa(等 IO)两列,你现在看到的这两列,就是上面这张时间线的真实版。
常见误区
- 【CPU 空转就是浪费,所以进程越多越好】。加到一定程度后,每件事的等待都会变长,而利用率几乎不再涨——收益递减。
- 【利用率 100% 是好事】。它只说明 CPU 没闲着,不说明干的活有用——死循环也能把利用率跑到 100%。
- 【IO 等待不算成本】。对单个进程来说,等就是等,它占的是用户感受到的那段时间,一点没省。
自测三问(点开对答案)
1. 单进程每跑 3 拍等 4 拍,利用率大约多少?为什么? —— 五成多一点(3/7 左右)。**上限由 IO 比例决定**,不是 CPU 能力。 2. 三个同样的进程,为什么总耗时不到单进程的三倍? —— 因为 A 等 IO 时 B 或 C 在跑,**原来空转的拍被填上了**。这就是多道设计的收益。 3. 从 CPU 占用率能判断什么? —— 占用满→瓶颈在 CPU;占用低但响应慢→瓶颈在 IO 或锁。**先看这个再决定往哪查。**- ☐ 4.4 思考
相关笔记
- 实验程序的实现 — 跑实验用的模拟器
- 思考 — 从这张时间线能推出什么
- 吞吐量与延迟 — 收益的边界在哪
- 🐧 Linux是怎样工作的 · 索引