文章目录
2.8 线程(Threads)的基本概念
2.8.1 线程的引入
在 OS 中引入进程的目的是为了使多个程序能并发执行,以提高资源利用率和系统吞吐量。
在操作系统中再引入线程,则是为了减少程序在并发执行时所付出的时空开销,使 OS 具有更好的并发性。
(1)进程的两个基本属性
- 进程是一个可拥有资源的独立单位
- 进程是一个可独立调度和分派的基本单位
(2)程序并发执行所需付出的时空开销
- 创建进程,系统在创建一个进程时,必须为它分配其所必需的、除处理机以外的所有资源,如内存空间、I/O设备,以及建立相应的 PCB
- 撤消进程,系统在撤消进程时,又必须先对其所占有的资源执行回收操作,然后再撤消 PCB
- 进程切换,对进程进行上下文切换时,需要保留当前进程的 CPU 环境,设置新选中进程的 CPU 环境,因而须花费不少的处理机时间
由于进程是个资源的拥有者,因而在创建、撤消和切换中,系统必须为之付出较大的时空开销。这就限制了系统中所设置进程的数目,而且进程切换也不宜过于频繁,从而限制了并发程度的进一步提高。
(3)线程——作为调度和分派的基本单位(*)
2.8.2 线程与进程的比较
(1)调度的基本单位
-
进程
在传统的 OS 中,作为独立调度和分派的基本单位,因而进程是能独立运行的基本单位。在每次被调度时,都需要进行上下文切换,开销较大。
-
线程
作为调度和分派的基本单位,因而线程是能独立运行的基本单位。当线程切换时,仅需保存和设置少量寄存器内容,切换代价远低于进程。
在同一进程中,线程的切换不会引起进程的切换,但从一个进程中的线程切换到另一个进程中的线程时,必然就会引起进程的切换。
(2)并发性
-
线程
不仅进程之间可以并发执行,而且在一个进程中的多个线程之间亦可并发执行,甚至还允许在一个进程中的所有线程都能并发执行。同样,不同进程中的线程也能并发执行。
这使得 OS 具有更好的并发性,从而能更加有效地提高系统资源的利用率和系统的吞吐量。
(3)拥有资源
-
进程
进程可以拥有资源,并作为系统中拥有资源的一个基本单位。
-
线程
线程本身并不拥有系统资源,而是仅有一点必不可少的、能保证独立运行的资源。线程除了拥有自己的少量资源外,还允许多个线程共享该进程所拥有的资源。
(4)独立性
在同一进程中的不同线程之间的独立性要比不同进程之间的独立性低得多。
这是因为,为防止进程之间彼此干扰和破坏,每个进程都拥有一个独立的地址空间和其它资源,除了共享全局变量外,不允许其它进程的访问。但是同一进程中的不同线程往往是为了提高并发性以及进行相互之间的合作而创建的,它们共享进程的内存地址空间和资源。
(5)系统开销
-
进程
在创建或撤消进程时,系统都要为之分配和回收进程控制块、分配或回收其它资源。OS为此所付出的开销,明显大于线程创建或撤消时所付出的开销。
在进程切换时,涉及到进程上下文的切换,而线程的切换代价也远低于进程的。
-
线程
线程的创建要比进程的创建快 30 倍,而线程上下文切换要比进程上下文的切换快 5 倍。
此外,由于一个进程中的多个线程具有相同的地址空间,线程之间的同步和通信也比进程的简单。因此,在一些 OS 中,线程的切换、同步和通信都无需操作系统内核的干预。
(6)支持多处理及系统
单线程进程:在多处理机系统中,进程只能运行在1个处理机上
多线程进程:在多处理机系统中,进程可以在多个处理机上并发运行,无疑加速了进程了完成
2.8.3 线程的状态和线程控制块
(1)线程运行的三个状态
- 执行状态:表示线程已获得处理机而正在运行;
- 就绪状态:指线程已具备了各种执行条件,只须再获得 CPU 便可立即执行;
- 阻塞状态:指线程在执行中因某事件受阻而处于暂停状态。
线程状态之间的转换和进程状态之间的转换是一样的。
(2)线程控制块 TCB
系统也为每个线程配置了一个线程控制块TCB,将所有用于控制和管理线程的信息记录在线程控制块中。线程控制块通常有这样几项:
-
线程标识符
为每个线程赋予一个唯一的线程标识符;
-
一组寄存器
包括程序计数器 PC、状态寄存器和通用寄存器的内容;
-
线程运行状态
用于描述线程正处于何种运行状态
-
优先级
描述线程执行的优先程度;
-
线程专有存储区
用于线程切换时存放现场保护信息,和与该线程相关的统计信息等;
-
信号屏蔽
即对某些信号加以屏蔽;
-
堆栈指针
在线程运行时,经常会进行过程调用,而过程的调用通常会出现多重嵌套的情况,这样,就必须将每次过程调用中所使用的局部变量以及返回地址保存起来。为此,应为每个线程设置一个堆栈,用它来保存局部变量和返回地址。
(3)多线程 OS 中的进程属性
- 进程作为系统资源分配的基本单位
- 多个线程可并发执行
- 进程已经不是可执行的实体,而是把线程作为独立运行(调度)的基本单位
2.9 线程的实现
2.9.1 线程的实现方式
(1)内核支持线程 KST
内核支持线程就是在内核的支持下运行的,即无论是用户进程中的线程,还是系统进程中的线程,他们的创建、撤消和切换等,也是依靠内核实现的。此外,在内核空间还为每一个内核支持线程设置了一个线程控制块, 内核是根据该控制块而感知某线程的存在的,并对其加以控制。
![](https://i-blog.csdnimg.cn/blog_migrate/73ef57ebb356b6f43200bb2da79bfeb2.png)
主要优点:
- 在多处理器系统中,内核能够同时调度同一进程中的多个线程并行执行;
- 如果进程中的一个线程被阻塞了,内核可以调度该进程中的其它线程占有处理器运行,也可以运行其它进程中的线程;
- 内核支持线程具有很小的数据结构和堆栈,线程的切换比较快,切换开销小;
- 内核本身也可以采用多线程技术,可以提高系统的执行速度和效率。
主要缺点:
对于用户的线程切换而言,其模式切换的开销较大,在同一个进程中,从一个线程切换到另个线程时,需要从用户态转到核心态进行,这是因为用户进程的线程在用户态运行,而线程调度和管理是在内核实现的,系统开销较大。
(2)用户级线程 ULT
用户级线程是在用户空间中实现的。对线程的创建、 撤消、同步与通信等功能,都无需内核的支持,即用户级线程是与内核无关的。在一个系统中的用户级线程的数目可以达到数百个至数千个。由于这些线程的任务控制块都是设置在用户空间,而线程所执行的操作也无需内核的帮助,因而内核完全不知道用户级线程的存在。
注意,用户级线程的系统,其调度仍是以进程为单位进行的。在使用轮转调度算法时,由于每个进程轮流执行一个时间片,因此线程多的进程平均运行时间反而少,这实质上并不公平。相比之下,内核支持线程并不会出现这种现象。
![](https://i-blog.csdnimg.cn/blog_migrate/512aeead542e51bf5ffb96bf3cf17120.png)
主要优点:
- 线程切换不需要转换到内核空间,节省了模式切换的开销
- 调度算法可以是进程专用的,因为内核中并没有设置线程
- 用户级线程的实现与 OS 平台无关,因为对于线程管理的代码是属于用户程序的一部分,所有的应用程序都可以对之进行共享
主要缺点:
- 系统调用的阻塞问题。在基于进程机制的 OS 中,大多数系统调用将使进程阻塞。因此,当线程执行一个系统调用时,不仅该线程被阻塞,而且进程内的所有线程会被阻塞。
- 内核一次只为一个进程分配一个 CPU,即进程无法享用多处理器带来的好处。因此,进程中仅有一个线程能执行,其它线程只能等待其放弃 CPU。
(3)组合方式 ULT/KST
组合方式能够结合 KST 和 ULT 两者的优点,克服了其各自的不足。
根据用户级线程和内核支持线程连接方式的不同,形成了三种模型:
-
多对一模型
将用户线程映射到一个内核控制线程。线程的调度和和管理在用户空间中完成,仅当线程需要访问内核时,才进行这种映射。
优点:
- 开销小
- 效率高
缺点:
- 和 ULT 缺点类似,线程在访问内核时若被阻塞,则整个用户进程都会被阻塞。
- 一次只能有一个线程访问内核
-
一对一模型
将每一个用户级线程分别映射到一个内核支持线程。
唯一缺点:
- 系统开销较大,需要限制整个系统的线程数
-
多对多模型
将许多用户线程映射到数个内核线程上。这种方式结合了以上两种模型的优点。
![](https://i-blog.csdnimg.cn/blog_migrate/dad622c33ec8a3db89def98525dce66d.png)
2.9.2 线程的实现
(1)KST 的实现
![](https://i-blog.csdnimg.cn/blog_migrate/95a9964c4acb0fe5c7f2a6fcc7c4a725.png)
系统在创建新的进程时,便为它分配一个任务数据区,其中包括若干个线程控制块 TCB 。每个 TCB 可保存线程标识符、优先级、CPU 状态信息等,而且 TCB 这些信息保存在内核空间中。当创建一个线程时,分配一个 TCB,当撤销一个线程时,回收该线程的所有资源和 TCB。
(2)ULT 的实现
-
运行时系统
运行时系统实质上就是用于管理和控制线程的函数集合。正因为此,用户级线程才能与内核无关。运行时系统中所有函数都在用户空间,作为用户级线程与内核之间的接口。
传统的 OS 在进程切换时必须经过“用户态—核心态—用户态”的过程,但是用户级线程的切换则不必转入核心态,这使得其切换速度非常快。
-
内核控制线程
这种线程又称为轻型进程 LWP(Light Weight Process)。一个进程可以有多个 LWP,每个 LWP 都有自己的数据结构(如 TCB),LWP 也可以共享进程所拥有的资源。LWP 通过系统调用来获得内核提供的服务,这种实现方式就是组合方式。
系统中为了节省开销,不能设置太多的 LWP,因此将这些 LWP 做成一个缓冲池,即“线程池”。进程中的任一用户线程都可以连接到 LWP 池中的任意一个 LWP 上,通过这些 LWP 来访问内核,但内核看到的总是多个 LWP 而不是用户级线程。这样就实现了内核与用户级线程之间的隔离,从而使用户级线程与内核无关。
![](https://i-blog.csdnimg.cn/blog_migrate/f8b6d60d9137c3b989877c4133ea3110.png)
2.9.3 线程的创建和终止
(1)线程的创建
应用程序在启动时,通常仅有一个线程在执行,称为“初始化线程”,主要功能是用于创建新线程。
在创建新线程时,需要利用一个线程创建函数(或系统调用),提供相应的参数,完成后返回一个线程标识符。
(2)线程的终止
终止线程也是通过调用相应的函数(或系统调用)对它进行终止操作。
大多数的 OS 中,线程终止后并不立即释放资源,只有进程中的其他线程执行了分离函数后,被终止的线程才与线程分离,否则资源仍能够被其他线程利用。
被终止但仍未释放资源的线程仍有可能被其他线程调用,以使其重新恢复运行。因此,需要调用一条“等待线程终止”的连接命令来与该线程进行连接:
- 若指定线程尚未被终止,则调用者会被阻塞
- 若指定线程被终止,则调用者与其进行连接并执行。