1. 引入线程

首先回忆一下为什么会有进程 —— 在以前,程序是串行执行的,为了让多道程序并发执行,引入了进程。进程虽然显著提高了资源利用率和系统吞吐量,满足了并发的需求,但是这种并发能不能做得更好呢?事实上,进程既是一个携带资源的独立单位,也是独立调度的基本单位,因此,在进程的创建、撤销和切换时,系统必须为之付出较大的时间空间开销(没办法“轻装上阵”)。鉴于此,系统不宜设置过多的进程,也不宜频繁地切换进程,这对于并发来说是一种限制。

如何解决这个问题呢?可以把进程看作是管理初创公司的老板,一开始人手不足,老板既要管理公司,也要四处奔跑沟通业务;但是一旦人手充足,那么老板仍然可以管理公司,只是沟通业务的工作就可以交给手下人去执行了。同理,我们可以考虑依然让进程作为拥有资源的独立单位,但是独立调度的基本单位则不再是进程,而是新引入的线程了。

2. 线程与进程

调度的基本单位

引入线程后,调度的基本单位不再是进程,而是线程。线程能够独立运行,且切换的时候,代价远远小于进程切换的代价。同一进程不同线程的切换,不会引起进程的切换。

执行的基本单位

通常认为进程不再作为可执行的实体。也即,可以说进程处于“执行”状态,但其实指的是该进程的某个线程正在执行;可以说进程处于“挂起”状态,但其实指的是该进程的所有线程都被挂起。其他同理。

并发性

进程间仍然能够并发,不仅如此,一个进程中的多个线程间也能并发,不同进程中的线程也能够并发,大大提高了 OS 的并发性。

资源

资源依然掌握在进程手中。为了性能考虑,线程仅占有一点必不可少的资源(比如 TCB,程序计数器等)。那么如何访问其它资源呢?事实上,同一进程的线程共享该进程所拥有的资源。另外,这些线程还共享同一片内存地址空间,所以也可以方便地进行通信。

独立性

同一进程中的线程间独立性要比不同进程间独立性低很多。前者独立性高,因为要防止进程之间彼此干扰和破坏;后者独立性低,因为同一进程的多个线程通常需要协作完成任务,互相之间可访问程度相对来说会比较高。

系统开销

在创建和撤销进程时,系统需要分配或者回收 PCB,分配或者回收资源,所以需要付出一定的时空开销;但是线程的创建和撤销的时空开销则明显小很多,尤其是在同一进程内的线程创建和撤销,这种开销会更加地小。

支持多处理机系统

传统的单线程进程,即使处理机再多,一个进程也只能运行在一个处理机上;但是引入了线程后,一个进程的多个线程可以分配到多个处理机上、并行执行。

3. 线程的状态和线程控制块

线程的状态类似于进程的状态,同样有:执行态、就绪态、阻塞态。

进程有进程控制块 PCB,线程也有线程控制块 TCB(Thread control block)。TCB 记录了所有用于控制和管理线程的信息。具体来说包括:

① 线程标识符:为每个线程赋予一个唯一的线程标识符

② 组寄存器:包括程序计数器PC、状态寄存器和通用寄存器的内容

③ 线程运行状态:用于描述线程正处于何种运行状态
④ 优先级:描述线程执行的优先程度

⑤ 线程专有存储区:用于线程切换时存放现场保护信息,和与该线程相关的统计信息等

⑥ 信号屏蔽:即对某些信号加以屏蔽

⑦ 堆栈指针:在线程运行时,经常会进行过程调用,而过程的调用通常会出现多重嵌套的情况,这样,就必须将每次过程调用中所使用的局部变量以及返回地址保存起来。为此,应为每
个线程设置一个堆栈,用它来保存局部变量和返回地址。相应地,在 TCB 中,也须设置两
个指向堆栈的指针:指向用户自己堆栈的指针和指向核心栈的指针。前者是指当线程运行
在用户态时,使用用户自己的用户栈来保存局部变量和返回地址,后者是指当线程运行在
核心态时使用系统的核心栈。

4. 线程的实现

不同的系统对于线程的实现方式是不同的。

4.1 用户级线程

用户级线程是由应用程序通过线程库实现的,所有的线程管理工作(包括对线程的创建、撤销等)都由应用程序来完成,无需操作系统内核的干预,操作系统内核也意识不到用户级线程的存在。

它的优点在于:

  • 正如前面所说的,应用程序一个人包办了线程的管理工作,所以进程无需切换到内核态来对线程进行管理。
  • 不同的进程可以根据自己的需要选择不同的调度算法,对自己的线程进行管理和调度

缺点在于:

  • 当线程执行一个系统调用而使自己陷入阻塞时,其它与自己同处于一个进程的线程也都会被阻塞

4.2 内核级线程

与用户级线程中应用程序全权进行线程管理不同,在内核级线程中,线程的管理是由操作系统内核来完成的。尤其是线程的切换,因为它是由内核完成的,所以需要在核心态下进行操作。

它的优点在于:

  • 当线程执行一个系统调用而使自己陷入阻塞时,其它与自己同处于一个进程的线程不会被阻塞,依然可以被调用。而且还可以选择调用其它进程中的线程。

缺点在于:

  • 用户的线程切换需要一定的开销。虽然用户进程的线程在用户态下运行,但是是内核进行的线程管理,所以要切换线程,首先必须先从用户态转到核心态。

4.3 组合方式

有的系统同时结合了用户级线程和内核级线程,将多个用户级线程映射到多个内核级线程上。根据映射方式的不同,分为三种模型:

(1)多对一模型

将同一个进程的多个用户级线程映射到一个内核级线程。优点在于线程的切换直接由应用程序完成,无需切换到核心态;缺点在于一个线程的阻塞将会导致整个进程阻塞。

(2)一对一模型

将同一个进程的每一个用户级线程映射到对应的每一个内核级线程。优点在于一个线程阻塞时,可以调用另一个线程。缺点在于需要为每个用户级线程分配一个对应的内核级线程,相当于一个进程就需要分配多个内核级线程,并且线程切换也需要在核心态下进行,这些都带来了比较大的开销。

(3)多对多模型

多个用户级线程对应多个内核级线程(用户级线程数目大于等于内核级线程数目)。优点在于,一个线程的阻塞不会再像多对一模型那样导致整个进程的阻塞,而且一个用户进程也不会再像一对一模型那样占用过多的内核级线程。


操作系统系列学习笔记:

操作系统学习笔记-1:基础概念

操作系统学习笔记-2:体系结构和运行机制

操作系统学习笔记-3:初识进程和进程控制

操作系统学习笔记-4:进程同步与进程互斥(一)

操作系统学习笔记-5:进程同步与进程互斥(二):信号量机制

操作系统学习笔记-6:进程同步与进程互斥(三):经典问题

操作系统学习笔记-7:进程通信

操作系统学习笔记-8:线程

操作系统学习笔记-9:调度