这是操作系统进程系列文章第三篇-操作系统线程描述 文章是《操作系统-精髓与设计原理》学习笔记

线程(thread)

什么是线程

线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

关于进程的两个概念:

  • 资源所有权:一个进程包括一个存放进程映像的虚拟地址空间(进程映像是程序、数据、栈和进程控制块中定义的属性的集合)。一个进程总是拥有对资源的控制或所有权,这些资源包括内存、I/O 通道,I/O 设备和文件。
  • 调度/执行:一个进程沿着通过一个或多个程序的一条执行路径执行,其执行过程可能与其他进程的执行过程交替执行。一个进程具有一个执行状态和一个分片的优先级,并且是一个可被操作系统调度和分配的实体。

这两个概念是独立的,操作系统可以独立的处理。

现代操作系统通常把分派单位称为线程(或轻量级进程),拥有资源所有权的单位称为进程。

多线程

多线程是指操作系统在单个进程内支持多个并发执行路径的能力。每个进程中只有一个线程在执行的方法称为单线程方法。进程支持多个线程的情况被称作多线程。

在多线程环境中,进程被定义成资源分配的单位和一个被保护的单位,与进程相关联的有:

  • 存放进程映像的虚拟地址空间
  • 受保护的对处理器、其他进程、文件和 I/O 资源的访问

在一个进程中,可能有一个或多个线程,每个线程有:

  • 线程的执行状态(运行,就绪)
  • 在未运行时保存的线程上下文
  • 一个执行栈
  • 用于每个线程局部变量的静态存储空间
  • 与进程内的其他线程共享的对进程的内存和资源的访问

进程 VS 线程

下图说明了进程和线程的区别:

线程和进程的区别

在单线程模型中,进程的标出包括他的进程控制块和用户地址空间,以及在进程执行中管理调用/返回 行为的用户栈和内核栈。当进程被控制时,处理器寄存器被该进程锁控制;当进程不运行时,这些处理器寄存器的内容被保存。

在多线程环境中,进程仍然只有一个与之关联的进程控制块和用户地址空间。但是每个线程都有一个独立的栈,还有独立的控制块用于包含寄存器值、优先级和其他与线程相关的状态信息。

进程中的所有线程共享该进程的状态和资源,它们驻留在同一块地址空间中,并且可以访问到相同的数据。当一个线程改变了内存中的一个数据项时,其他线程在访问这一数据项时能够看到变化后的结果。

线程的优点

  1. 在一个已有的进程中创建一个新的线程比创建一个全新的进程所需时间要少的多。
  2. 终止一个线程比终止一个进程花费的时间少
  3. 同一个进程内线程间切换比进程间切换花费的时间要少。
  4. 线程提高了不同的执行程序间通信的效率。(大多数操作系统中,独立进程间的通信需要内核的介入,由于同一进程中的线程共享内存和文件,它们间的通信无需调用内核)

线程状态

和进程一样,线程的关键状态有运行态、就绪态和阻塞态。挂起是进程级别的概念,一个进程被换出,它的所有线程都被换出。

有4个与线程状态改变相关的操作:

  • 派生:当派生一个新进程时,同时也为改进程派生出一个线程。进程中的线程也可以在同一个进程中派生另一个线程,新的线程拥有自己的寄存器上下文和栈空间,且被放置在就绪队列中。
  • 阻塞:当线程需要等待一个事件时,将被阻塞,此时处理器转而执行另一个就绪线程(可能是同一进程,也可能是不同进程)
  • 解除阻塞:当阻塞一个线程的事件发生时,该线程被转移到就绪队列中
  • 结束:当一个线程完成时,其寄存器上下文和栈都被释放。

用户级线程和内核级线程

线程的实现可以分为两大类:用户级线程(User-Level Thread ULT)内核级线程(Kernel-Level Thread KLT)

用户级线程和内核级线程

在用户级线程和内核级线程使用时,通常有以下三种模式:

用户级线程和内核级线程

在一个纯粹的用户级线程程序中,有关线程管理的所有工作都由应用程序完成,内核意识不到线程的存在。

使用用户级线程的优点:

  1. 线程切换不需要内核态特权,因此,进程不需要为了线程管理而切换到内核态,这节省了两次状态转换(从用户态到内核态,再从内核态返回用户态)的开销。
  2. 调度可以是用户程序相关的。(可以为特定的应用使用特定的调度算法)
  3. 用户级线程可以在任何操作系统中运行,不需要对底层内核进行修改以支持用户级线程。

使用用户级线程的缺点:

  1. 许多系统调用会被阻塞。因此当用户级线程执行一个系统调用时,不仅这个线程会被阻塞,进程中所有线程都会被阻塞。
  2. 不能使用多个处理器。内核一次只把一个进程分配给一个处理器,因此一个进程中只有一个线程可以执行。

解决这两个问题有两种方式:

  1. 使用多进程代替多线程,但这样消除了多线程的优势
  2. 使用 jacketing 技术。把一个产生阻塞的系统调用转换成一个非阻塞的系统调用。

在一个纯粹的内合辑线程程序中,有关线程管理的所有工作都由内核完成。内核为进程及其内部的每个线程维护上下文信息。调度由内核基于线程完成。

使用内核级线程客服了用户级线程的两个基本缺陷。首先内核可以把同一个进程的多个线程调度到多个处理器;其次一个进程中的线程被阻塞,内核可以调度同一个进程的另一个线程。

主要缺点是:把控制从一个线程传送到同一个进程内的另一个线程是,需要内核的状态切换。

线程和进程的执行时间

某些操作系统提供了一种组合的用户级/内核级线程设施。在组合的系统中,线程创建完全在用户空间中完成,线程的调度和同步也是在应用程序中进行。一个应用程序中的多个用户级线程被映射到一些(小于或等于用户级线程的数目)内核级线程上。开发者可以为特定的应用程序和处理器调节内核级线程的数目,以达到最佳结果。

Linux 的进程和线程管理

Linux中的进程或任务由一个 task_struct数据结构表示,这个数据结构包含了以下信息:

  • 状态:进程的执行状态
  • 调度信息:Linux 调度进程所需的信息
  • 标识符
  • 进程间通信:Linux 支持 UNIX SVR4中的 IPC 机制。
  • 时间和计时器:包括进程创建的时刻和进程所消耗的处理器时间总量
  • 文件系统:包括指向被该进程打开的任何文件的指针和指向该进程当前和根目录的指针。
  • 地址空间:定义分配给该进程的虚拟空间
  • 处理器专用上下文:构成改进程上下文的寄存器和栈信息
  • 停止:进程被终止,并且只能由来自另一个进程的主动动作恢复
  • 僵死:进程已被终止,但由于某些原因,在进程表中仍然有它的任务结构

Linux 提供一种不区分进程和线程的解决方案,用户级线程被映射到内核级进程上。组成一个用户级进程的多个用户级线程被映射到共享同一组 ID 的多个 Linux 内核级进程上。这使得这些进程可以共享文件和内存等资源,使得同一组中的进程调度切换是不需要切换上下文。

在 Linux 中通过复制当前进程的属性可创建一个新进程。新进程被克隆出来,使得它可以共享资源。当两个进程共享相同虚拟内存时,它们可以被当做是一个进程中的线程。因此 Linux 中进程和线程没有区别。

参考链接


最后,感谢女朋友支持和包容,比❤️

想了解以下内容可以在公号输入相应关键字获取历史文章: 公号&小程序 | 设计模式 | 并发&协程

关注 赞赏