操作系统5:线程的概念和线程实现

目录

1、线程的概念

(1)进程和进程并发是所需要付出的时空开销

(2)线程和进程的比较

(3)线程的状态和线程的控制块

2、线程的实现

(1)线程的实现方式

(2)内核支持线程的实现

(3)用户级线程的实现

(4)线程的创建和终止


1、线程的概念

        在 OS 中引入进程的目的是为了使多个程序能并发执行,以提高资源利用率和系统吞吐量,那么,在操作系统中再引入线程,则是为了减少程序在并发执行时所付出的时空开销,使 OS 具有更好的并发性。// 引入线程为了让系统具有更好的并发性

(1)进程和进程并发是所需要付出的时空开销

        1 - 进程的两个基本属性

  • 进程是一个可拥有资源的独立单位,它拥的资源,包括用于存放程序正文、数据的磁盘和内存地址空间,以及它在运行时所需要的 I/O 设备、已打开的文件、信号量等。
  • 进程同是一个可独立调度和分派的基本单位,每个进程在系统中有唯一的 PCB,系统可根据其 PCB 感知进程的存在,也可以根据其 PCB 中的信息,对进程进行调度,还可将断点信息保存在其 PCB 中。反之,再利用进程 PCB 中的信息来恢复进程运行的现场。

        2 - 程序并发执行所需付出的时空开销

        为使程序能并发执行,系统必须进行以下的一系列操作:// 主要在上下文切换

  • 创建进程,系统在创建一个进程时,必须为它分配其所必需的、除处理机以外的所有资源,如内存空间、I/O 设备,以及建立相应的 PCB。
  • 撤消进程,系统在撤消进程时,又必须先对其所占有的资源执行回收操作,然后再撤消 PCB。
  • 进程切换,对进程进行上下文切换时,需要保留当前进程的 CPU 环境,设置新选中进程的 CPU 环境,因而须花费不少的处理机时间。

        3 - 线程——作为调度和分配的基本单位

        原因:为了使多个程序更好地并发执行,同时又尽量减少系统的开销。

        思想:将进程拥有资源独立调度两个属性进行分开,减少拥有资源单位的频繁切换。

// 进程的切换“太重”。上图中,如果使用进程作为基本调度单位(左图),那么上下文切换时,整个 PCB 的数据都要进行切换。但是如果使用线程作为数据的基本调度单位,上下文切换时,并不需要切换整个 PCB,而是切换对应的 TCB 即可,减少了上下文切换时间。

(2)线程和进程的比较

        线程具有许多传统进程所具有的特征,所以又称之为轻型进程(Light-WeightProcess),相应地,把传统进程称为重型进程(Heavy-Weight Process),相当于只有一个线程的任务。

        1 - 调度的基本单位

  • 传统 OS 中:进程作为独立调度和分派的基本单位(独立运行)。在每次被调度时,都需要进行上下文切换,开销较大。// 传统 OS 没有线程概念
  • 在引入线程的 OS 中:线程作为调度和分派的基本单位(独立运行)。线程切换时仅需保存和设置少量寄存器内容,切换代价远低于进程。

        同一进程中,线程的切换不会引起进程的切换,但从一个进程中的线程切换到另一个进程中的线程时,必然就会引起进程的切换。// 不同进程的线程切换必然会进行进程切换

        2 - 并发性

        在引入线程的 OS 中,不仅进程之间可以并发执行,而且在一个进程中的多个线程之间也可并发执行,同样,不同进程中的线程也能并发执行。所以引入线程的 OS 具有更好的并发性,从而能更加有效地提高系统资源的利用率和系统的吞吐量。

        3 - 拥有资源

        进程可以拥有资源,并作为系统中拥有资源的一个基本单位。然而,线程本身并不拥有系统资源,而是仅有一点必不可少的、能保证独立运行的资源。比如,在每个线程中都应具有一个用于控制线程运行的线程控制块 TCB、用于指示被执行指令序列的程序计数器保留局部变量、少数状态参数和返回地址等的一组寄存器和堆栈。//进程拥有资源,线程被独立调度

        多个线程共享该进程所拥有的资源。比如,属于同一进程的所有线程都具有相同的地址空间,线程可以访问该地址空间中的每一个虚地址。此外,还可以访问进程所拥有的资源,如已打开的文件、定时器、信号量机构等的内存空间和它所申请到的 I/O 设备等。

        4 - 独立性

        线程之间的独立性要比进程之间的独立性低得多。这是因为为防止进程之间彼此干扰和破坏,每个进程都拥有一个独立的地址空间和其它资源,除了共享全局变量外,不允许其它进程的访问。而线程是为了提高并发性以及进行相互之间的合作而创建的,它们共享进程的内存地址空间和资源,如每个线程都可以访问它们所属进程地址空间中的所有地址,如一个线程的堆栈可以被其它线程读写,甚至完全清除。由一个线程打开的文件可以供其它线程读、写等。

        5 - 系统开销

        在创建或撤消进程时,系统都要为之分配和回收进程控制块、分配或回收其它资源,如内存空间和 I/O 设备等。OS 为此所付出的开销,明显大于线程创建或撤消时所付出的开销。类似地,在进程切换时,涉及到进程上下文的切换,而线程的切换代价也远低于进程。// 进程系统开销大

        6 - 支持多处理机系统

        在多处理机系统中,对于传统的进程,即单线程进程,不管有多少处理机,该进程只能运行在一个处理机上。但对于多线程进程,就可以将一个进程中的多个线程分配到多个处理机上,使它们并行执行,这无疑将加速进程的完成。// 多核并行

(3)线程的状态和线程的控制块

        1 - 线程运行的三个状态

        与传统的进程一样,在各线程之间也存在着共享资源和相互合作的制约关系,致使线程在运行时也具有间断性。相应地,线程在运行时也具有下述三种基本状态:

  • 执行状态,表示线程已获得 CPU 而正在运行。
  • 就绪状态,指线程已具备了各种执行条件,只须再获得 CPU 便可立即执行。
  • 阻塞状态,指线程在执行中因某事件受阻而处于暂停状态,例如,当一个线程执行从键盘读入数据的系统调用时,该线程就被阻塞。

        线程状态之间的转换和进程状态之间的转换是一样的。

        2 - 线程控制块 TCB

        如同每个进程有一个进程控制块一样,系统也为每个线程配置了一个线程控制块 TCB 将所有用于控制和管理线程的信息记录在线程控制块中。

        线程控制块通常有这样几项:

  • 线程标识符:为每个线程赋予一个唯一的线程标识符。
  • 一组寄存器:包括程序计数器 PC 状态寄存器和通用寄存器的内容。
  • 线程运行状态:用于描述线程正处于何种运行状态。
  • 优先级:描述线程执行的优先程度。
  • 线程专有存储区:用于线程切换时存放现场保护信息,和与该线程相关的统计信息等。
  • 信号屏蔽:即对某些信号加以屏蔽。
  • 堆栈指针:线程运行时,经常会进行过程调用,而过程的调用通常会出现多重嵌套的情况,这样,就必须将每次过程调用中所使用的局部变量以及返回地址保存起来。为此,每个线程都有一个堆栈,用来保存局部变量和返回地址。相应地,在 TCB 中,还有两个指向堆栈的指针:指向用户自己堆栈的指针和指向核心栈的指针。前者是指当线程运行在用户态时,使用用户自己的用户栈来保存局部变量和返回地址,后者是指当线程运行在核心态时使用系统的核心栈// 线程分为核心态和用户态,不同状态下有不同的堆栈

        3 - 多线程 OS 中的进程属性

        多线程 OS 支持在一个进程中的多个线程可以并发执行,此时的进程就不再是一个执行的实体。多线程 OS 中的进程有以下属性:

  • 进程是一个可拥有资源的基本单位。在多线程 OS 中,进程仍是作为系统资源分配的基本单位。// 不再是独立的执行单位
  • 多个线程可并发执行。一个进程都含有若千个相对独立的线程,其数目可多可少,但至少要有一个线程。进程为这些线程提供资源及运行环境,使它们能并发执行。在 OS 中的所有线程都只能属于某一个特定进程。// 线程隶属于进程
  • 进程已不是可执行的实体。在多线程OS 中,把线程作为独立运行或调度的基本单位。此时进程已不再是一个基本的可执行实体。但是,进程仍具有与执行相关的状态。例如,进程处于“执行”状态,实际上是指该进程中的某线程正在执行。此外,对进程施加的有关操作也对其线程起作用。例如,在把某个进程挂起时,该进程中的所有线程也都将被挂起,把某进程激活时,属于该进程的所有线程也都将被激活。// 对进程的操作同样会影响线程

2、线程的实现

        线程已在许多系统中实现,但各系统的实现方式并不完全相同。在有的系统中,例如数据库管理系统,如 infomix 所实现的是用户级线程;而另一些系统(如 Macintosh 和 OS/2 操作系统)所实现的是内核支持线程;还有一些系统如 Solaris 操作系统,则同时实现了这两种类型的线程。

(1)线程的实现方式

        1 - 内核支持线程 KST(Kernel Supported Threads)

        在 OS 中,系统进程和用户进程,都是在操作系统内核的支持下运行的,与内核紧密相关。内核支持线程 KST 同样也是在内核的支持下运行的,它的创建、阻塞、撤消和切换等,都是在内核空间实现的。为了对内核线程进行控制和管理,在内核空间为每一个内核线程设置了一个线程控制块,内核根据该控制块感知某线程的存在,并对其加以控制。

        内核线程实现方式的四个优点:

  • 在多处理器系统中,内核能够同时调度同一进程中的多个线程并行执行。
  • 如果进程中的一个线程被阻塞了,内核可以调度该进程中的其它线程占有处理器运行,也可以运行其它进程中的线程。
  • 内核支持线程具有很小的数据结构和堆栈,线程的切换比较快,切换开销小
  • 内核本身也可以采用多线程技术,可以提高系统的执行速度和效率。

        内核支持线程的主要缺点:

        对于用户的线程切换而言,其模式切换的开销较大,在同一个进程中,从一个线程切换到另一个线程时,需要从用户态转到内核态,因为用户进程的线程在用户态运行,而线程调度和管理是在内核实现的,系统开销较大。// 相对于用户线程来说,线程调度和管理系统开销较大

        2 - 用户级线程 ULT(User LevelThreads)

        用户级线程是在用户空间中实现的。对线程的创建、 撤消、同步与通信等功能,都无需内核的支持,即用户级线程是与内核无关的。在一个系统中的用户级线程的数目可以达到数百个至数千个。由于这些线程的任务控制块都是设置在用户空间,而线程所执行的操作也无需内核的帮助,因而内核完全不知道用户级线程的存在。

        值得说明的是,对于设置了用户级线程的系统,其调度仍是以进程为单位进行的。在采用轮转调度算法时,各个进程轮流执行一个时间片,这对诸进程而言貌似是公平的。但假如在进程 A 中包含了一个用户级线程,而在另一个进程 B 中含有 100 个用户级线程,这样,进程 A 中线程的运行时间将是进程 B 中各线程运行时间的 100 倍。相应地,其速度要快上100 倍,因此说实质上并不公平。// 轮转调度算法下,每个进程中的线程执行时间并不一样,线程越多,执行时间越少

        假如系统中设置的是内核支持线程,则调度便是以线程为单位进行的。在采用轮转法调度时,是各个线程轮流执行一个时间片。同样假定进程 A 中只有一个内核支持线程,而在进程 B 中有 100 个内核支持线程。此时进程 B 可以获得的 CPU 时间是进程 A 的 100 倍且进程 B 可使100 个系统调用并发工作。// 以上两个例子对于理解内核级和用户级线程的区别很重要

        用户级线程方式的优点:

  • 线程切换不需要转换到内核空间。对一个进程而言,其所有线程的管理数据结构均在该进程的用户空间中,管理线程切换的线程库也在用户地址空间运行,因此进程不必切换到内核方式来做线程管理,从而节省了模式切换的开销。
  • 调度算法可以是进程专用的。在不干扰 OS 调度的情况下,不同的进程可以根据自身需要选择不同的调度算法,对自己的线程进行管理和调度,而与 OS 的低级调度算法是无关的。
  • 用户级线程的实现与 OS 平台无关,因为对于线程管理的代码是属于用户程序的一部分,所有的应用程序都可以对之进行共享。因此,用户级线程甚至可以在不支持线程机制的操作系统平台上实现。// 完全程序化,与操作系统无关

        用户级线程方式的缺点:

  • 系统调用的阻寒问题。在基于进程机制的 OS 中,大多数系统调用将使进程阻塞。因此,当线程执行一个系统调用时,不仅该线程被阻塞,而且,进程内的所有线程会被阻塞。而在内核支持线程方式中,则进程中的其它线程仍然可以运行。// 线程批量阻塞
  • 在单纯的用户级线程实现方式中,多线程应用不能利用多处理机进行多重处理的优点,内核每次分配给一个进程的仅有一个 CPU,因此,进程中仅有一个线程能执行,在该线程放弃 CPU 之前,其它线程只能等待。// 一个进程中仅有一个线程能执行,丧失多核优势

        3 - 组合实现方式

        有些 OS 把用户级线程和内核支持线程两种方式进行组合,提供了组合方式 ULT/KST 线程

        在组合方式线程系统中,内核支持多个内核支持线程的建立、调度和管理,同时,也允许用户应用程序建立、调度和管理用户级线程。一些内核支持线程对应多个用户级线程,这是用户级线程通过时分多路复用内核支持线程来实现的。即将用户级线程对部分或全部内核支持线程进行多路复用,程序员可按应用需要和机器配置,对内核支持线程数目进行调整,以达到较好效果。

        组合方式线程中,同一个进程内的多个线程可以同时在多处理器上并行执行,而且在阻塞一个线程时并不需要将整个进程阻塞。所以,组合方式多线程机制能够结合 KST 和 ULT 两者的优点,并克服了其各自的不足。// 组合方式 -> 取长补短

        由于用户级线程和内核支持线程连接方式的不同,从而形成了三种不同的模型:多对一模型、一对一模型和多对多模型。

        多对一模型:即将用户线程映射到一个内核控制线程。这些用户线程一般属于一个进程,运行在该进程的用户空间,对这些线程的调度和管理也是在该进程的用户空间中完成。仅当用户线程需要访问内核时,才将其映射到一个内核控制线程上,但每次只允许一个线程进行映射。该模型的主要优点是线程管理的开销小,效率高;其主要缺点在于,如果一个线程在访问内核时发生阻塞,则整个进程都会被阻塞;此外,在任一时刻,只有一个线程能够访问内核,多个线程不能同时在多个处理机上运行。

        一对一模型:即将每一个用户级线程映射到一个内核支持线程。为每一个用户线程都设置一个内核控制线程与之连接。该模型的主要优点是:当一个线程阻塞时,允许调度另一个线程运行,所以它提供了比多对一模型更好的并发功能。此外,在多处理机系统中,它允许多个线程并行地运行在多处理机系统上。该模型的唯一缺点是:每创建一个用户线程,相应地就需要创建一个内核线程,开销较大,因此需要限制整个系统的线程数

        多对多模型:即将许多用户线程映射到同样数量或更少数量的内核线程上。内核控制线程的数目可以根据应用进程和系统的不同而变化,可以比用户线程少,也可以与之相同。该模型结合上述两种模型的优点,它可以像一对一模型那样,使一个进程的多个线程并行地运行在多处理机系统上,也可像多对一模型那样,减少线程的管理开销和提高效率。// 前两种模型的折中方案,取长补短

(2)内核支持线程的实现

        在仅设置了内核支持线程的 OS 中,系统在创建一个新进程时,便为它分配一个任务数据区 PTDA(Per TaskDataArea),其中包括若干个线程控制块 TCB 空间。在每一个 TCB 中可保存线程标识符优先级、线程运行的 CPU 状态等信息。虽然这些信息与用户级线程 TCB 中的信息相同,但现在却是被保存在内核空间中// 为进程分配 PTDA,TCB 被保存在内核空间中

        每当进程要创建一个线程时,便为新线程分配一个 TCB,将有关信息填入该 TCB 中,并为之分配必要的资源.。当 PTDA 中的所有 TCB 空间已用完,而进程又要创建新的线程时,如果它所创建的线程数未超过系统的允许值(通常为数十至数百个),那么系统可再为之分配新的 TCB 空间。 当撤消线程时,也应回收该线程的所有资源和 TCB。// 资源空间的分配和回收

        内核支持线程的调度和切换与进程的调度和切换十分相似,也分抢占式方式和非抢占方式两种。在线程的调度算法上,同样可采用时间片轮转法、 优先权算法等。当线程调度选中一个线程后,便将处理机分配给它。当然,线程在调度和切换上所花费的开销要比进程的小得多。// 内核线程的调度和切换与进程十分相似

(3)用户级线程的实现

        用户级线程是在用户空间实现的。所有的用户级线程都具有相同的结构,它们都运行在一个中间系统上。实现中间系统两种方式:运行时系统内核控制线程// 用户空间实现

        1 - 运行时系统(RuntimeSystem)

        所谓 “运行时系统”,实质上是用于管理和控制线程的函数的集合,其中包括用于创建和撤消线程的函数、线程同步和通信的函数,以及实现线程调度的函数等。正因为有这些函数,才能使用户级线程与内核无关。运行时系统中的所有函数都驻留在用户空间并作为用户级线程与内核之间的接口。// 用户线程是通过用户程序实现的

        在传统的 OS 中,进程在切换时必须先由用户态转为核心态,再由核心来执行切换任务。而用户级线程在切换时则不须转入内核态,而是由运行时系统中的线程切换函数,来执行切换任务,该过程将线程的 CPU 状态保存在该线程的堆中,然后按照一定的算法,选择一个处于就绪状态的新线程运行,将新线程堆栈中的 CPU 状态装入到 CPU 相应的寄存器中,一旦将栈指针和程序计数器切换后,便开始了新线程的运行。由于用户级线程的切换无须进入内核,且切换操作简单,因而使用户级线程的切换速度非常快// 线程比进程更加轻量,切换的资源较少

        不论在传统的 OS 中,还是在多线程 OS 中,系统资源都是由内核管理的。在传统的 OS 中,进程利用 OS 提供的系统调用来请求系统资源,系统调用通过软中断机制进入 OS 内核,由内核来完成相应资源的分配。而用户级线程不能利用系统调用,当线程需要系统资源时,将该请求传送给运行时系统,由运行时系统通过相应的系统调用来获得系统资源。// 用户线程需要通过运行时系统获取系统资源

        2 - 内核控制线程

        这种线程又称为轻型进程 LWP(Light Weight Process)。每一个进程都可拥有多个 LWP,同用户级线程一样,每个LWP 都有自己的数据结构(如TCB)。LWP 可以共享进程所拥有的资源,通过系统调用来获得内核提供的服务。当一个用户级线程运行时,只须将它连接到一个 LWP 上,此时它便具有了内核支持线程的所有属性。这种线程实现方式就是组合方式。

        系统中的用户级线程数量可能很大,但是 LWP 是有限的。所以可以把有限数量的 LWP 做成一个缓冲池,即线程池”。线程池中的 LWP 可以进行复用,用户级线程需要利用 LWP 才能与内核进行通信

        内核只能看到 LWP 而看不到用户级线程,这是因为 LWP 实现了内核与用户级线程之间的隔离,从而使用户级线程与内核无关。// LWP 看起来像是用户线程和内核之间的代理人

         当用户级线程不需要与内核通信时,并不需要 LWP;而当要通信时,便须借助于 LWP;而且每个要通信的用户级线程都需要一个 LWP。例如,在一个任务中,如果同时有 5 个用户级线程发出了对文件的读、写请求,这就需要有 5 个 LWP 来协助,由 LWP 将文件的读、写请求发送给相应的内核级线程,再由后者执行具体的读、写操作。如果一个任务中只有 4 个LWP,则只能有 4 个用户级线程的读、写请求被传送给内核线程,余下的一个用户级线程必须等待。// 注意,LWP 并不是内核线程,而是内核线程和用户级线程之间的桥梁

        如果内核级线程发生阻塞,则与之相连接的多个 LWP 也将被阻塞,进而使连接到 LWP 上的用户级线程也被阻塞。// 连锁反应:内核级线程 -> LWP -> 用户级线程

        如果进程中只有一个 LWP,那么进程也会被阻塞。这种情况就跟传统的 OS 一样,在进程执行系统调用时,该进程实际上是阻塞的。但如果进程中含有多个 LWP,则当一个 LWP 阻塞时,进程中的另一个 LWP 仍可继续执行;如果进程中的所有 LWP 全部阻塞,进程中的线程也仍然能继续执行,只是不能再去访问内核// 不进行I/O和设备访问,就可以避开内核态

(4)线程的创建和终止

        和进程一样,线程也具有生命期,它由创建而产生,由调度而执行,由终止而消亡。相应的,在 OS 中也就有用于创建线程的函数(或系统调用)和用于终止线程的函数(或系统调用)。

        1 - 线程的创建

        应用程序在启动时,通常仅有一个线程在执行,这个线程称为 “初始化线程”,它的主要功能是用于创建新线程。在创建新线程时,需要利用一个线程创建函数(或系统调用),并提供相应的参数,如指向线程主程序的入口指针、堆栈的大小,以及用于调度的优先级等。在线程的创建函数执行完后,将返回一个线程标识符供以后使用。// 线程创建后将返回一个线程标识符

        2 - 线程的终止

        当一个线程完成任务后,或是线程在运行中出现异常情况而须被强行终止时,由终止线程通过调用相应的函数对它执行终止操作。不过有些线程(主要是系统线程),它们一旦被建立起来之后,便一直运行下去而不被终止。

        在大多数的 OS 中,线程被中止后并不立即释放它所占有的资源,只有当进程中的其它线程执行了分离函数后,被终止的线程才与资源分离,此时的资源才能被其它线程利用。虽已被终止但尚未释放资源的线程仍可以被需要它的线程所调用,以使被终止线程重新恢复运行// 线程复用

        为此,调用线程须调用一条被称为 “等待线程终止” 的连接命令来与该线程进行连接。如果在一个调用者线程调用 “等待线程终止” 的连接命令,试图与指定线程相连接时,若指定线程尚未被终止,则调用连接命令的线程将会阻塞,直至指定线程被终止后,才能实现它与调用者线程的连接并继续执行;若指定线程已被终止,则调用者线程不会被阻塞而是继续执行。

猜你喜欢

转载自blog.csdn.net/swadian2008/article/details/131286717