进程和线程
进程
- 进程实体(进程映像)由PCB、程序段和数据段组成,其中PCB是进程存在的唯一标志。
线程
- 线程最直接的理解就是“轻量级进程”,它是一个基本的CPU执行单元,包含CPU现场(状态),也是程序执行流的最小单元,由线程ID、程序计数器、寄存器集合和堆栈组成。一个线程可以创建和撤销另一个线程,同一进程中的多个线程之间可以并发执行。由于线程之间的相互制约,致使线程在运行中呈现出间断性。线程也有就绪、阻塞和运行三种基本状态。
- 引入线程后,进程只作为除CPU外的系统资源的分配单元,而线程则作为处理机的分配单元。一个进程内部有多个线程。
线程与进程的比较/属性/特点
0)多线程操作系统中的进程已不再是一个基本的执行实体,但它仍具有与执行相关的状态。所谓进程处于“执行”状态,实际上是指该进程中的某线程正在执行。
1) 调度。在传统的操作系统中(引入线程之前),拥有资源和独立调度的基本单位都是进程,每次调度都要进行上下文切换,开销较大。
在引入线程的操作系统中,线程是独立调度的基本单位, 而线程切换的代价远低于进程。在同一进程中,线程的切换不会引起进程切换。但从一个进程中的线程切换到另一个进程中的线程时,会引起进程切换。
什么是上下文切换?
在传统的操作系统中,进程是拥有资源和独立调度的基本单位。当一个进程在执行时,它拥有系统的全部资源,并且可以独立地执行任务。然而,当操作系统需要进行任务调度时,它需要将当前正在执行的进程的上下文(即该进程的程序计数器、寄存器状态、内存数据等)保存下来,然后加载下一个进程的上下文到系统中,这个过程就叫做上下文切换。
2) 并发性。在引入线程的操作系统中,不仅进程之间可以并发执行,而且一个进程中的多
个线程之间亦可并发执行,甚至不同进程中的线程也能并发执行,从而使操作系统具有
更好的并发性,提高了系统资源的利用率和系统的吞吐量。
无论是进程还是线程,并发性的目的是为了提高系统资源的利用率和系统的吞吐量
3) 拥有资源。线程是进程中的一个实体,是被系统独立调度和分派的基本单位。进程是系统中拥有资源的基本单位,而线程不拥有系统资源(仅有一点必不可少、能保证独立运行的资源,比如:每个线程都有一个唯一的标识符和一个线程控制块,线程控制块记录了线程执行的寄存器和栈等在那个时刻的状态),但线程可以访问其隶属进程的系统资源,这主要表现在属于同一进程的所有线程都具有相同的地址空间。要知道,若线程也是拥有资源的单位,则切换线程就需要较大的时空开销,线程这个概念的提出就没有意义。
时空开销是指在进行进程或线程切换时,系统需要保存和恢复上下文信息以及分配和释放资源所需要的时间和空间。
如果线程也是拥有资源的单位,切换线程需要较大的时空开销,主要是因为线程的切换涉及到资源的分配和释放。而这些资源的分配和释放的进行需要时间以及系统的空间,所以加大了时空开销。
4) 独立性。每个进程都拥有独立的地址空间和资源,除了共享全局变量,不允许其他进程访问。某进程中的线程对其他进程不可见。同一进程中的不同线程是为了提高并发性及进行相互之间的合作而创建的,它们共享进程的地址空间和资源。
5) 系统开销。在创建或撤销进程时,系统都要为之分配或回收进程控制块PCB及其他资源,如内存空间、I/O设备等。操作系统为此所付出的开销,明显大于创建或撤销线程时的开销。类似地,在进程切换时涉及进程上下文的切换,而线程切换时只需保存和设置少量寄存器内容,开销很小。此外,由于同一进程内的多个线程共享进程的地址空间,因此这些线程之间的同步与通信非常容易实现,甚至无须操作系统的干预。
6) 支持多处理机系统。对于传统单线程进程,不管有多少处理机,进程只能运行在一个处理机上。对于多线程进程,可以将进程中的多个线程分配到多个处理机上执行。所以,一个多线程进程可以使用多个处理机,这些处理机分别是进程中的多个线程使用的。
7)不同的线程可以执行相同的程序。当某一个功能,被不同的用户或在不同的事件调用时,执行这个功能的线程可能是不同的。
8)线程是处理机的独立调度单位,多个线程是可以并发执行的。在单CPU的计算机系统中,
各线程可交替地占用CPU;在多CPU的计算机系统中,各线程可同时占用不同的CPU,
若各个CPU同时为一个进程内的各线程服务,则可缩短进程的处理时间。
9) 一个线程被创建后,便开始了它的生命周期,直至终止。线程在生命周期内会经历阻塞
态、就绪态和运行态等各种状态变化。
10)为什么线程的提出有利于提高系统并发性?
可以这样来理解:由于有了线程,线程切换时, 有可能会发生进程切换,也有可能不发生进程切换,平均而言每次切换所需的开销就变小了,因此能够让更多的线程参与并发,而不会影响到响应时间等问题。
线程的状态和转换
与进程一样,各线程之间也存在共享资源和相互合作的制约关系,致使线程在运行时也具有间断性。相应地,线程在运行时也具有下面三种基本状态。
- 执行状态:线程己获得处理机而正在运行。
- 就绪状态:线程已具备各种执行条件,只需再获得CPU便可立即执行。
- 阻寒状态:线程在执行中因某事件受阻而处于暂停状态。
线程这三种基本状态之间的转换和进程基本状态之间的转换是一样的。
线程的组织与控制
- 线程控制块
与进程类似,系统也为每个线程配置一个线程控制块TCB,用于记录控制和管理线程的信息。
线程控制块通常包括:①线程标识符;②一组寄存器,包括程序计数器、状态寄存器和通用寄存器;③线程运行状态,用于描述线程正处于何种状态;④优先级;⑤线程专有存储区,线程切换时用于保存现场等;⑥堆栈指针,用于过程调用时保存局部变量及返回地址等。
同一进程中的所有线程都完全共享进程的地址空间和全局变量。各个线程都可以访问进程地
址空间的每个单元,所以一个线程可以读、写或甚至清除另一个线程的堆栈。
2. 线程的创建
线程也是具有生命期的,它由创建而产生,由调度而执行,由终止而消亡。相应地,在操作系统中就有用于创建线程和终止线程的函数(或系统调用)。 用户程序启动时,通常仅有一个称为“初始化线程”的线程正在执行,其主要功能是用于创建新线程。在创建新线程时,需要利用一个线程创建函数,并提供相应的参数,如指向线程主程序的入口指针、堆栈的大小、线程优先级等。线程创建函数执行完后,将返回一个线程标识符。
3. 线程的终止
当一个线程完成自己的任务后,或线程在运行中出现异常而要被强制终止时,由终止线程调用相应的函数执行终止操作。但是有些线程(主要是系统线程)一旦被建立,便一直运行而不会被终止。通常,线程被终止后并不立即释放它所占有的资源,只有当进程中的其他线程执行了分离函数后,被终止线程才与资源分离,此时的资源才能被其他线程利用。
被终止但尚未释放资源的线程仍可被其他线程调用,以使被终止线程重新恢复运行。
线程的实现方式
- 用户级线程ULT
在用户级线程中, 有关线程管理(创建 、 撤销和切换等)的所有工作都由应用程序在用户空
间中完成 , 内核意识不到线程的存在 。 应用程序可以通过使用线程库设计成多线程程序 。 通常 ,
应用程序从单线程开始 , 在该线程中开始运行 , 在其运行的任何时刻 , 可以通过调用线程库中的
派生例程创建一个在相同进程中运行的新线程 。
对于设置了用户级线程的系统, 其调度仍是以进程为单位进行的 , 各个进程轮流执行一个时
间片 。 假设进程 A 包含 1 个用户级线程 , 进程 B 包含 100 个用户级线程 , 这样 , 进程 A 中线程
的运行时间将是进程 B 中各线程运行时间的 100 倍 , 因此对线程来说实质上是不公平的 。
这种实现方式的优点如下: ①线程切换不需要转换到内核空间 , 节省了模式切换的开销 。②调 度算法可以是进程专用的 , 不同的进程可根据自身的需要 , 对自己的线程选择不同的调度算法。 ③用户级线程的实现与操作系统平台无关 , 对线程管理的代码是属于用户程序的一部分 。
这种实现方式的缺点如下 : ①系统调用的阻塞问题 , 当线程执行一个系统调用时 ,不仅该线程被阻塞 , 而且进程内的所有线程都被阻塞 。 ②不能发挥多处理机的优势 , 内核每次分配给一个
进程的仅有一个 CPU, 因此进程中仅有一个线程能执行 。
2. 内核级线程KLT
在操作系统中, 无论是系统进程还是用户进程 , 都是在操作系统内核的支持下运行的 , 与内
核紧密相关 。内核级线程同样也是在内核的支持下运行的 , 线程管理的所有工作也是在内核空间
内实现的 。 内核空间也为每个内核级线程设置一个线程控制块, 内核根据该控制块感知某线程的
存在 , 并对其加以控制。
这种实现方式的优点如下: ①能发挥多处理机的优势 , 内核能同时调度同一进程中的多个线
程并行执行 。 ②如果进程中的一个线程被阻塞 , 内核可以调度该进程中的其他线程占用处理机 ,
也可运行其他进程中的线程 。 ③内核支持线程具有很小的数据结构和堆栈 , 线程切换比较快 、 开
销小 。 ④内核本身也可采用多线程技术 , 可以提高系统的执行速度和效率 。
这种实现方式的缺点如下: 同一进程中的线程切换 , 需要从用户态转到核心态进行 , 系统开
销较大 。 这是因为用户进程的线程在用户态运行 , 而线程调度和管理是在内核实现的 。
3. 组合方式
有些系统使用组合方式的多线程实现 。 在组合实现方式中 , 内核支持多个内核级线程的建立 、
调度和管理 , 同时允许用户程序建立 、 调度和管理用户级线程 。 一些内核级线程对应多个用户级
线程 , 这是用户级线程通过时分多路复用内核级线程实现的 。 同一进程中的多个线程可以同时在
多处理机上并行执行 , 且在阻塞一个线程时不需要将整个进程阻塞 , 所以组合方式能结合 KLT 和
ULT 的优点 , 并且克服各自的不足 。
在线程实现方式的介绍中, 提到了通过线程库来创建和管理线程 。 线程犀 (thread library)
是为程序员提供创建和管理线程的 API 。 实现线程库主要的方法有如下两种 :
①在用户空间中提供一个没有内核支持的库 。 这种摩的所有代码和数据结构都位于用户空间
中 。 这意味着 , 调用库内的一个函数只导致用户空间中的一个本地函数的调用 。
②实现由操作系统直接支持的内核级的一个库 。 对于这种情况 , 库内的代码和数据结构位于
内核空间 。 调用库中的一个 API 函数通常会导致对内核的系统调用 。
目前使用的三种主要线程库是 : POSIX Pthreads 、 Windows APL、 Java.,
Pthreads 作为POSIX 标准的扩展 , 可以提供用户级或内核级的库 。 Windows API 是用于 Windows 系统的内核级线程库 。
Java 线程 API 允许线程在 Java 程序中直接创建和管理 。 由于 JVM 实例通常运行在宿主操作系统
之上 , Java 线程 API 通常采用宿主系就的线程库来实现 , 因此在 Windows 系统中 Java 线程通常
采用 Windows API 来实现 , 在类 UNIX 系统中采用 Pthreads 来实现 。
多线程模型
有些系统同时支持用户线程和内核线程 , 由于用户级线程和内核级线程连接方式的不同 , 从
而形成了下面三种不同的多线程模型 。
1 ) 多对一模型。 将多个用户级线程映射到一个内核级线程 。
这些用户线程 一般属于一个进程 , 线程的调度和管理在用户空间完成 。 仅当用户线程需要访问内核时, 才将其映射到一个内核级线程上 , 但是每次只允许一个线程进行映射 。
优点: 线程管理是在用户空间进行的 , 因而效率比较高 。
缺点: 如果一个线程在访问内核时发生阻塞 , 则整个进程都会被阻塞 ; 在任何时刻 ,只 有一个线程能够访问内核 , 多个线程不能同时在多个处理机上运行 。
2) 一对一模型 。 将每个用户级线程映射到一个内核级线程 。
优点:当一个线程被阻塞后 , 允许调度另一个线程运行 , 所以并发能力较强 。
缺点: 每创建一个用户线程 , 相应地就需要创建一个内核线程 , 开销较大 。
3) 多对多模型 。 将n个用户线程映射到 m 个内核级线程上 , 要求 n>=m
特点 : 既克服了多对一模型并发度不高的缺点 , 又克服了一对一模型的一个用户进程占
用太多内核级线程而开销太大的缺点 。 此外 , 还拥有上述两种模型各自的优点 。