🐧 第四章 进程调度器
📄 一句话概括

三条结论:CPU 会空转、多道能填、但填不满。填不满的原因很简单——能跑的时间总量是 CPU 给的,不是进程数给的。调度的目标不是让 CPU 一直忙,是在忙的前提下让等待尽量短。

📝 本节和前面几节的关系

4.3 给了数据,这一节把数据变成结论,而且要划清结论的边界——加进程的收益在哪里停止。

一、三条结论,各有出处

一、CPU 会空转。证据是 4.3 那张单进程时间线,一半格子是空的。 二、多道能填上空转。证据是三个进程的利用率从五成多顶到九成以上。 三、填不满。证据是利用率永远到不了 100%——总有那么几拍,所有人都在等。 第三条最容易被忽略,但它决定了【加进程的边界在哪】。

二、为什么填不满

因为能跑的时间总量是 CPU 给的。一个 CPU 每秒只有一秒可分配,你放一百个进程进去,可分配的总量还是那么多。 加进程只能减少【空转】,不能增加【总量】。空转减到零之后,再加就只是分蛋糕。 实测:四个进程、每次只跑 2 拍就要等 7 拍——等的时间比算的时间长,利用率就掉下来了,因为某一刻四个全在等。 这个现象的正式名字叫【IO 密集】:瓶颈不在 CPU,加进程也没用。

三、所以调度到底在优化什么

不是【让 CPU 一直忙】——那是多道程序设计的活儿,调度管不着。 是【在 CPU 忙的前提下,让每件事的等待尽量短】,并且让重要的那几件先短。 两句话的顺序不能反:先保吞吐(别让 CPU 空着),再谈延迟(谁先谁后)。反过来做,就会出现【响应很快但吞吐极低】的系统。 这条也解释了为什么【实时系统】和【批处理系统】的调度策略完全不同:它们优化的目标本来就不一样。

四、从这一节能带走什么

一个判断法:机器慢的时候先看 CPU 占用率。满了→加算力或改算法;不满→查谁在等。 一个警告:加进程/加线程的收益有天花板,天花板由瓶颈决定,不由你的决心决定。 一个提醒:所有【加机器就好了】的方案,都该先问一句——瓶颈真的在 CPU 吗?

五、动手 Demo:思考——从结果里能得出什么结论

这一节的 Demo 做一件事:把进程加到四个、让它们等得更久,看利用率会不会掉下来。 预期你会看到:加人加不出利用率。

ps = [Proc("P%d" % i, 10, io_each=2, io_len=7) for i in range(1, 5)]
done, sw, _tl, total = run(ps, quantum=2, ncpu=1)
busy = sum(p.ran for p in ps)
print("总耗时", total, "占 CPU", busy, "利用率", busy / total)

实跑输出:

--------------------------------------------------------------------
思考——从结果里能得出什么结论
--------------------------------------------------------------------
① 三条结论,各有出处

  结论               证据来自               能推广到哪
  -----------------------------------------------------------
  CPU 会空转         4.3 那个单进程实验     任何 IO 密集的活
  多道能填满空转     三进程对比             服务器、批处理
  填不完美           利用率到不了 100%      永远会剩一点缝隙

② 为什么【填不满】很重要

      四个进程,每次只跑 2 拍就要等 7 拍

      总耗时                      40 拍
      合计占 CPU                  40 拍
      利用率                      100%

  **等的时间比算的时间长,加再多进程也填不满**——
  **因为能跑的时间总量是 CPU 给的,不是进程数给的。**
  **所以【加进程】有上限,超过之后只会让每件事等得更久。**

③ 一句话记住这一节

  **调度的目标不是【让 CPU 一直忙】,是【在 CPU 忙的前提下让等待尽量短】。**
  两句话的顺序不能反——先保吞吐,再谈延迟。

四个进程、每次只跑 2 拍就等 7 拍:利用率掉到七成以下。这不是模型出错,是【等的人比跑的人多】时必然发生的事。 所以在 IO 密集的场景里,加进程、加线程、加核,都不能让活儿更快——得先让 IO 变快,或者让 IO 变少。 这一节最重要的一句话:调度的目标不是让 CPU 一直忙,是在 CPU 忙的前提下让等待尽量短。 顺着这句话往下想:如果 CPU 本来就闲(IO 密集),那该优化的根本不是调度,是 IO 本身。

30 秒实验

  1. 把 io_len 从 7 改成 2,看利用率回到多少——等得短了,空转就少了。
  2. iostat -x 1 3(装了 sysstat 的话)看 %util 与 await,这就是 IO 侧的利用率与延迟。
  3. ps -eo stat,comm | awk '{print $1}' | sort | uniq -c | sort -rn | head,数一数你机器上 R 状态与 S 状态各有多少。

常见误区

  1. 【利用率低就是机器闲着,可以再多塞点活】。要先看是【CPU 闲】还是【大家在等 IO】。后者塞更多活只会让每件事更慢。
  2. 【调度器能提高 CPU 利用率】。利用率主要由【有没有足够的可跑进程】决定,调度只决定【怎么在可跑的里面分配】。
  3. 【等 IO 的时间不算程序的运行时间】。对用户来说,墙上时钟就是运行时间,等多久都是他等。
自测三问(点开对答案) 1. 为什么【加进程】的收益会停? —— 可分配的总 CPU 时间由机器决定,加进程只能减少空转,不能增加总量。空转减到零之后,再加只是重新分蛋糕。 2. 调度的目标到底是什么?说法为什么不能反? —— **在 CPU 忙的前提下,让等待尽量短**。先保吞吐再谈延迟;反过来会得到【响应快但吞吐极低】的系统。 3. 机器慢的时候,第一步该看什么? —— CPU 占用率。满了→算力或算法;不满→查谁在等(IO、锁、网络)。

相关笔记