Categories
程式開發

程序原本(四):系统的基础部件


编者按:本文节选自周爱民著《程序原本》一书中的部分章节。

系统应付规模问题只有两个总法则

聪明的曹冲称出了大象的重量。此前我们仅仅将这一思想归纳为“通过某种系统将大象的重量映射为石头的重量”,但这还远远不够。因为我们只触碰到了这个问题的一个解——映射,而“为什么需要映射”才真正是问题的本身。

曹冲的高明之处在于,他认识到“不能称象”的本质原因是:大象不能被分割。“不能分割”才是灾难之源。因此,如果系统如我们此前所讨论的,是“通过在程序组织上的结构化来解决规模问题”的一种策略,那么程序所解决的问题集“能否分割”以及“如何正确地分割”,就是所有系统问题的核心所在。

对此,曹冲称象的故事提出了一种可能的解:如果“被运算对象”是不可分割的,那么我们可以将它映射为可分割的对象。但即使这个解总是存在的,我们也只是看到了问题的一半。因为在曹冲称象的故事中,我们忽略了一个非常重要的事物:秤。

秤,实质上就是一个数据处理系统:

  • 其一,它具备一个数据处理系统的两个基本要素:处理信息与反馈信息,例如称一块肉,并反馈结果:二斤六两。
  • 其二,它存有一个基本限制:能够处理的信息边界,例如只能称重100斤。

也正是因为秤的数据处理能力有限,我们才称不出大象的重量。所以整个“称象问题”既可以看做是“象太大”1,也可以看做是“秤太小”。

1 可见“象太大”才是最原始、顶层的根本问题,所谓“大象不能分割”事实上是“化整为零”这个求解方案所带来的第二层问题;而“将大象重量映射为石头重量”,是进一步的求解方案。“映射/如何映射”作为第三层的问题,是远离原始问题的以及其本质的。

我们的计算机理论是基于这样一个事实,即计算系统的本质就是“算数”。而“被运算对象”的分割只是解决了其中“的问题”。因此当我们将逻辑上的计算系统映射为一个实际实现——例如“称象”或者“计算机”时,我们也可以尝试去解决“的问题”。

换言之,系统应付规模问题的总法则只有两个:

运算能力的分布,以及运算对象的分布。

分布的两个基本特性:可拆分与可处理

但什么是“分布”呢?

分布并不等于分割。“分割”是指一个问题集(无论是运算能力还是运算对象)能否被切分,例如前面一再提及的大象就是不可被分割的。而“分布”,指的是分割的结果能否被各个独立地加以处理。因此,我们说大象映射为石头之后具有了“(数据的)可分布性”,并不仅仅是因为在形式上进行了分割,还因为这些石头能被逐一称重。所以说:

“可拆分”与拆分的结果“可处理”,这两个特性在“分布”中缺一不可。

与一把秤相类似,一个函数实质上也是一个数据处理系统:

  • 其一,很明显,它能“处理数据”2并反馈一个返回值;

2 这其实并不那么明显。其一,处理(process)是函数的基本抽象含义,如同它在数学中的含义是求解;其二,数据(data)是处理的具体对象,这是函数入口参数的基本抽象含义,如同数学中的公式是函数,而代入公式的那些数才是求解的问题(即数据)。

  • 其二,函数能处理的数据也有类型、边界以及运算总量的限制。

就我们通常使用的、单处理器的个人计算机而言,“所有软件构成的全集”所提供的功能总和可以被理解为“一个函数”(设为函数F)。因为从计算机通电运行开始,系统开始了唯一一个程序入口与处理过程:

  • 步骤一:进行系统自检、BIOS预设等常规的、硬件系统自有的处理程序;
  • 步骤二:尝试按照约定次序加载移动存储、外部存储等设备中的处理程序3

3 引导光盘、引导软盘,以及硬盘活动分区的引导扇区等。

  • 步骤三:将系统的控制权转移给步骤二中找到的处理程序(的入口)。

请注意,在上述这个过程以及其后的全过程中,处理器(CPU)的处理其实是在单一的时间序列中进行的。而我们的操作系统之所以能同时运行多个程序(例如Windows的资源管理器与记事本),以及在后台与前台运行不同的服务与应用程序等,是操作系统:

  • 将“所有软件所提供的功能总和”,即是我们上面假定的函数$F$,分成了多个函数$F_0,cdots,F_n$,计为$F’$;
  • 将 $F’$ 理解为进程的入口,并假定 $F’$ 可以再分成多个函数$F’_0,cdots,F’_n$,计为$F’’$;
  • 将 $F’’$ 理解为线程的入口,并假定 $F’’$ 可以再分成……

可见我们的操作系统(或某个硬件环境下的软件系统)无非是在对需要运算的总量进行拆分,并尝试将这些拆分结果分布在“不同的逻辑单元”4中进行处理。

4 这里仅基于Windows系统的“进程-线程”模型进行讨论。事实上这些逻辑单元在不同系统中有着多种类型、多种层次与关系的抽象,例如作业(Job)、任务(Task)、进程(Process)、线程(Thread)、协和(Coroutine)、流(Flow)、事务(Work)、会话(Session)等。

这一计算模型在单处理器时代被称为“分时处理”,即通过任务调度,将单一处理器的处理能力分配给不同的函数。这些函数在宏观层面上是同时运行的进程、线程等执行体,即并行5;而在微观层面上是分时运行的、单处理器的时间序列下的一个时间片,即串行

5 注意这样的实现其实是基于“进程-线程”模型的并发(Concurrency),这些执行体只是在宏观上看来是并行的而已,并非真正意义上的、与串行(Sequential)相对的并行计算(Parallel, Parallel computing)。

回顾分时处理模型,其核心仍然基于“顺序机器”这一基本假设。与此相应地,其基本计算逻辑也是基于结构化程序设计观念,即可以将分支逻辑与循环逻辑统一为顺序逻辑的一个部分。进一步地,也可以将函数理解为一个子函数序列的连续运算。再进一步地,可以说:

如果子函数可以分布,则整个系统是可以分布的。

图书简介https://www.ituring.com.cn/book/2429

程序原本(四):系统的基础部件 1

相关阅读

程序原本(一):应用开发基础

程序原本(二):应用开发技术

程序原本(三):开发视角下的工程问题