Terence

Let's geek and art.

thread/process 的错误直观

对 thread 的刻画,通常的 CS(computer science)教科书采用的方式是用一段类似于“电流”的曲线来表示线程,然后“启发”你一旦这根电流穿过了某个 task,这个 task 就被神奇地执行了起来。

但这样的「类比暗示」存在极大的误导,仿佛 thread 本身具备施加“执行能力”的魔法,仿佛程序本身是依靠 thread 的 action 被得以执行的。

使用这样的“电流”类比,让我们很难回答如下一些问题:

*/ 如果 thread 就是 action 的话,那么 thread 的 blocked、sleep 又该是什么呢?

*/ process 的 utilization 为 50% 又应该是什么意思呢?按照以上的「类比暗示」,是意味着某段“电流”可以突然像「黑客帝国」中的场景那般被突然停止下来?某段电流可以在「流动/不流动」的中间创造出一种 50% 流动的状态?

*/ thread 的休息是什么意思呢?为什么将 thread 的 state 被修改为 blocked 就能让 thread 停下来?仿佛我们可以为某段“电流”打上标签,然后 OS(Operating System)就用其它魔法将它停下来?还是说“电流”就像汽车的引擎马达,能够让它不转?转动 50%?

*/ thread 的唤醒在这样的「类比暗示」中又该如何理解?让“睡着”的电流醒来?什么又叫做“睡着的”电流呢?即便是用汽车引擎做类比,什么叫做“睡着的”引擎?指的是不转的引擎吗?意思是 thread 不转了?还是 CPU 不转了?

当然,即便是你考虑到有所谓的 CPU 通过时间分片(time-sharing)的方式间歇性地为不同的 thread 提供运算能力,那么,当 thread 获取到这个运算能力的时候,到底是在干嘛?是获得能力后,将 CPU 的能力释放 50% 吗?还是说将获得的 CPU 的能力(即:获得了“电流”?)短暂地释放一把?又或者是让获得的 CPU 堵住?可是什么是堵住电流呢?什么是堵住间歇性获得的 CPU 的运算能力呢?你怎么能够堵住 CPU 的运算呢?意思是,当你将 thread 的 state 变成 blocked 后,就可以让被这个 thread 获得的 CPU 不动了?或者再换个问法,CPU 可以不动吗?CPU 有不动的时候吗?

显然,这些疑惑都来自于将 thread 看作“具备赋予魔法的电流”的错误直观。在这样的直观下,你只会感觉 thread 这个概念本身越来越神秘、玄幻,越来越难以搞清楚它是什么,难以搞清楚它和 CPU 的关系。

事实上,揭开这团乱麻的关键在于要意识到:thread/process 同时具备 task/executor 的二重属性。

对于存在于 OS 的程序来讲,如果没有获得 OS 分配的 thread/process,这程序就无法运行。在这个视角下,thread/process 确实是 executor。但是,对于 CPU 来讲,thread/process 却是“装载了「程序指令」以及 memory 资源相关参数”的 task。在 CPU 的视角下,thread/process 是在「程序指令」基础上多封装了一层的、具备更多 resource meta info 的 task。是对这个「二重属性」的模糊认识,造成了大部分人理解 thread/process 的根本障碍。

在普通 CS 教科书中的“电流”比喻,更多的其实是倾向于将 thread 看作是 executor。但事实上,从我们上述各种问题的讨论可以看出,仅仅是 executor 的视角其实根本无法解释很多问题。原因在于,从根本上来讲,thread 本身并不是真正的 executor,它依旧是 task。只不过,对于 OS 中的程序来讲,由于非得依靠它的“包装”才能执行,所以会近似地将其看作“运行资源”。

如此论述 thread/process 和 CPU 的关系可能还是很抽象,不如给出两个直观类比。

1、可以将 CPU 当做是港口的将货物装载到轮船的“运力”(如调度车),而 OS 中的程序就是“货物”。但是,在港口有个规定,调度车只允许运输“集装箱”。于是,但凡一件货物要被装载到轮船上,它就必须首先被装载进“集装箱”,从而间接获得被运输上轮船的“运力”。在这里,这个“集装箱”便是 thread/process。

从「货物」的视角来看,「集装箱」就代表「运力」,因为没有集装箱就意味着没有装载到轮船上的可能性。但另一方面,真正的“运力”其实源自于「调度车」(对应到 CPU),而这个集装箱对于调度车来讲依旧是一个 task,只不过是封装了货物(对应到「OS 中的程序」)的特殊 task。

2、可以考虑一堆依靠“上发条”才能动起来的玩具,如:上发条的「汽车玩具」、上发条的「旋转木马」、上发条的「啄木鸟玩具」。但这些发条玩具有一个共同特征,拧发条的螺帽有着非常特殊的「形状 A」。而另外有一台专门拧螺帽的机器,但它只能“拧动”成「形状 A」的螺帽。

那么,假设每个玩具必须拧够 10 圈才能完成某个游戏所设定的目标。那么,依靠这台拧螺帽的机器,让这三台机器分别完成拧 10 圈动起来的任务,就可能是:首先将「汽车玩具」拿到这台机器上拧 3 圈,先让它跑一会儿;再将「啄木鸟玩具」拿过来拧 5 圈让它动起来;再把「旋转木马」拿过来让这台机器拧 4 圈;如此交替进行,让它们逐步累积完成分别各自拧 10 圈的任务。

在这个思想模型中,这台机器便是 CPU。而它们共同的「形状 A」的「螺帽」便是 thread/process,而每个玩具就是具体的 OS 中的程序。并且,以上示例我们还顺便说明了什么是分时系统(time-sharing system),即:让每个 CPU 的 task(即:thread/process)间歇性地交替运转。

有了以上两个思想模型,让我们再回过头去考察开始时提出的一些让人疑惑的问题。

首先,我们可以回答的一个问题,对于 CPU 来讲,它其实永远没有不运行的时候。它永远在寻找可执行的任务(如:集装箱、拧发条的玩具),即便是它找不到可运行的 task,它也处在「找」的这个 action 中。也即是,它在一个大的无限循环中,不断地轮询是否有可以执行的 process。而如果没有呢?那就继续循环 polling。对它来讲,它永远在运行,永远是 100% 的使用率。

所以,当我们说 process 的使用率是 50%、当我们说 CPU 的任务曲线是 50% 的时候,其实指的并不是 CPU 的使用率,而是「单位采样时间」中,某个具体 process 真正获得执行的时间占比。

那什么又是 thread 的 blocked 或 sleep 呢?那无非就是说在集装箱上标识了一个状态:请「调度车」暂时放弃运输我;而在拧发条的模型中,也无非是玩具的螺帽上标识一个状态,请拧发条的机器暂时放弃拧我。自然,什么是 thread/process 的休息呢?那无非就是标识自己放弃被 CPU 执行。

综上所述,将 thread/process 看作”电流“一般的 executor 其实只是 half story,并且还是本质上错误的 half story。另外更重要的 another half story 是:thread/process 其实是 CPU 的 task。只是,由于 OS 的所有 task(即:程序/指令)必须被封装一层 thread/process 才能被 CPU 执行的这一特性(谁规定的这个特性呢?当然是 OS 了。所以,如果脱离 OS,也就没有了 thread/process 的区分,也就没有了「程序指令」必须被包裹成 thread/process 才能执行的限制),会让人近似认为 thread/process 就是 executor 本身。但,这只是并未触及到本质的误导“近似”。也即是在这个意义上,将 thread 以“电流”曲线的方式在 CS 教课书中做展示是具有高度误导性的。你需要认识到 thread/process 是具备 task/executor 的双重属性的。并且更重要的是,它本质上是 task,而不是 executor。



近期回顾

为何信奉/选择 value investing?

备忘录:「可口可乐」穿越商业周期的一些宏观考察

「小红书」背后的产品逻辑:从“为什么没有复杂的后台发布平台”谈起

 


喜歡我的文章嗎?
別忘了給點支持與讚賞,讓我知道創作的路上有你陪伴。

CC BY-NC-ND 2.0 版權聲明

看不過癮?

一鍵登入,即可加入全球最優質中文創作社區