从 OS 发展的角度谈谈进程与线程的区别

492 阅读9分钟

进程(Process)

进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。

出现的背景和要解决的问题

单道批处理系统

单道批处理系统将多个作业输入到磁带上,并在系统中使用监督程序(Monitor)对多个作业能够连续处理,每次处理时将磁带上的第一个作业装入内存,处理完成后再读入下一个作业,在一定程度上提高了系统资源的利用率和系统的吞吐量。
但在单道批处理系统中,I/O 请求是阻塞 CPU 执行的,并且每个程序独占所有内存空间,CPU 和内存等系统资源在单道批处理系统中得不到充分的利用。

多道批处理系统

为了解决单道批处理系统中资源利用率差的问题,多道批处理系统应运而生。
多道批处理系统一次将多个作业读入内存,在当前占有 CPU 的程序进行 I / O 操作时,CPU 资源就会被分配给其他程序,这样就可以保证 CPU 资源一直处于忙碌状态,并且内存资源也能同时被多个程序使用,解决了单道系统中的资源利用不充分的矛盾。

在该机制下,多个程序在同一时间段内能够交替执行,这就是我们常说的并发的概念。

但问题随之而来,程序并发执行时将会失去其封闭性,并会具有间断性运行结果不可再现的特征,这使得普通的程序无法并发执行。
举个简单的栗子,A 程序执行一段时间后,进入了等待 I / O 的状态,此时 CPU 资源被暂时分配给了 B 程序,当 A 程序的 I / O 结束,重新得到 CPU 资源时,CPU 懵了:我刚处理到哪了来着?

进程概念的引入就是为了解决上述问题,对执行的程序加以控制和描述,从而实现程序间的并发。即:进程是为了实现并发而出现的。

如果你有认真阅读上面的文字,可能会发现文中一会说处理作业,一会又说执行程序,那么这两个词之间有什么不同呢?
在单道批处理系统中,OS 只需要处理记录在卡带上的一行行代码,我们就简单的称其为作业,而在多道批处理系统中,一个程序可以包含多个进程,而一个进程又由多个部分组成,当然就不能简单的称呼其为作业啦。

进程的构成

那么进程是如何解决并发程序间的控制问题的呢?进程一般来说是对进程实体的简称,进程实体由以下三部分构成,:

  • 程序段
  • 相关的数据程序
  • 进程控制块(Process Control Block)
    • 进程标识符
      • 外部标识符
      • 内部标识符
    • 处理机状态
    • 进程调度信息
    • 进程控制信息

不难看出,解决程序的控制和描述的问题,都是由进程控制块实现的,所以创建进程,实质就是创建进程实体的 PCB,而撤销进程,就是撤销进程PCB

进程的定义

有了上面我们啰里啰嗦的一大坨前戏铺垫,我们终于能够给出进程的定义了:

进程是进程实体的运行过程,是系统进行资源分配和调度的一个独立单位。

即:

  1. 进程是系统进行资源分配的基本单位。
  2. 进程是系统进行资源调度的基本单位。

是不是感觉哪里不对,怎么跟我在别的地方看到的不一样啊老湿!别着急嘛兄得,罗马不是一天建成的,它是怎么变得不一样的,且听我细细道来...

进程的特点与局限性

  1. 进程拥有独立的地址空间,这使得多个进程之间界限明确,不必担心一个进程不小心覆盖另一个进程的虚拟内存。但这也使得想要在进程间共享状态信息时,不得不依赖进程间通信(IPC),而 IPC 的开销是很高的。
  2. 因为进程拥有独立的地址空间,所以创建进程时,需要为其分配其必须的除处理机外的所有资源,撤销时又要将所有资源回收,增加了时空开销。
  3. 进程进行上下文切换时,需要保留当前进程的 CPU 环境,设置新进程中的 CPU 环境,需要耗费较多处理机资源。

设想一个具有以下三个基本功能的程序:

  1. 从键盘获取多次输入
  2. 对输入进行一系列处理
  3. 根据处理结果显示相应的内容

在没有线程的概念的时候,我们只能做如下设计:

  1. 在 A 进程内完成所有三个功能,则进程内功能执行的流程为:
获取输入->处理->显示输出->获取输入->处理->显示输出->....

进程间虽然是并发的,但是在进程内,各功能的实现是相互阻塞的。

  1. 创建 A B C 三个进程分别实现程序的三个功能,则流程为:
获取输入->
          处理数据->
                    显示输出
          获取输入->
                    处理数据->
                              显示输出

即多个进程之间能够并发执行,但进程间存在频繁的切换

造成这种现象的原因便是:进程同时是资源分配和调度的基本单位,导致我们需要对实现不同功能的进程分别分配系统资源,再分别独立调度执行。而如果我们想要将多个功能所需的所有系统资源统一分配到一个进程中,则进程内部多个功能的执行是阻塞的,无法达到并发的效果。

为了解决这种矛盾,我们不再将进程作为独立调度的基本单位线程的概念应运而生。

线程(Threads)

线程,有时被称为轻量进程(Lightweight Process,LWP),是程序执行流的最小单元。一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。另外,线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。由于线程之间的相互制约,致使线程在运行中呈现出间断性。线程也有就绪、阻塞和运行三种基本状态。就绪状态是指线程具备运行的所有条件,逻辑上可以运行,在等待处理机;运行状态是指线程占有处理机正在运行;阻塞状态是指线程在等待一个事件(如某个信号量),逻辑上不可执行。每一个程序都至少有一个线程,若程序只有一个线程,那就是程序本身。 线程是程序中一个单一的顺序控制流程。进程内有一个相对独立的、可调度的执行单元,是系统独立调度和分派CPU的基本单位指令运行时的程序的调度单位。在单个程序中同时运行多个线程完成不同的工作,称为多线程。 线程是独立调度和分派的基本单位。 在引入线程的 OS 中,每个进程可以拥有多个线程,不仅进程间可以并发执行,线程间也可以并发执行,这就解决了我们上面提到的资源分配与调度的矛盾。

独立性

线程依附于进程而存在,一个进程可以拥有多个线程,线程本身并不拥有资源,但是它可以共享所在进程的资源。所以线程间独立性较弱,这为线程间通信带来了便利。

并发性

进程间并发的同时,进程中的多个线程也可以实现并发执行,不仅如此,不同进程中的多个线程也能实现并发执行,是不是有点屌呢?

系统开销

OS 创建与销毁进程时需要申请或释放对应的内存空间,而线程因为本身并不拥有资源,所以无需进行这一系列操作。
同时,因为属于同一进程的多个线程具有相同的地址空间,所以线程间的通信与同步远比进程简单。线程间上下文切换所需的时间要比进程间切换快 N 倍。

所以在上面的例子中,我们可以将多个功能所需的资源同一分配给一个进程,然后在进程中使用多个线程分别去处理每个功能,这样多个功能就能够多线程并发执行,并且不需要在进程间进行切换。

总结(或曰太长不看版)

  • 进程
    • 是为实现并发而出现的。
    • 是资源统一分配的基本单位。
    • 一个程序可以拥有多个进程进程间相互独立存在。
    • 进程拥有独立的内存空间,进程间的进行信息交换需要依赖进程间通信(IPC)。
    • 创建 / 销毁时需要开辟 / 释放内存空间
    • 上下文切换开销较大。
  • 线程
    • 是为了减少进程并发执行时的时空开销而出现的。
    • 是资源独立调度的基本单位,本身并不拥有资源。
    • 线程依附于进程而存在,独立性较低,一个进程可以拥有多个线程
    • 线程共享他所在的进程的资源,所以通信同步的时空开销较小。
    • 创建 / 销毁时无需对内存空间进行操作。
    • 上下文切换开销较小。
  • 其他
    • 进程所施加的所有状态也会对属于它的线程生效(挂起、激活等)。