CoderMrWu

生活诚可期,爱情价更高!

线程的基本概念以及线程的实现原理

操作系统中,进程是可以独立运行的基本单位。随着操作系统理论和技术的发展,到了80年代中期,人们又提出了比进程更小的、能够独立运行的基本单位——线程线程可以提高系统内程序并发执行的级别,可以进一步提高系统效率。由于线程的这些优点,近几年来线程的概念获得了广泛的应用。人们不仅在新推出的操作系统中引入了线程概念,而且在数据库管理系统和其他一些应用软件中,也通过引入线程来改善系统的性能。

本节简要地叙述线程的基本概念,并且对进程和线程进行比较,从而为读者今后更深入地理解、掌握和运用线程,打下一个初步的基础。

一、线程的基本概念

如果说在操作系统中引入进程的目的是为了使多个程序并发执行,以改善资源利用率及提高系统效率,那么,在操作系统中再引入线程,则是为了减少程序并发执行时所付出的时间和空间开销,使操作系统具有更好的并发性

进程具有两个基本属性,即进程是一个可拥有资源的独立单位;进程同时又是一个可以独立调度和分派的基本单位。正是由于进程具有这两个基本属性,才使之成为一个能独立运行的基本单位,从而也构成了进程并发执行的基础。

然而为使程序能并发执行,系统还必须进行以下的一系列操作。

1)创建进程。系统在创建一个进程时,必须为其分配其所需的所有资源(除处理器外),包括内存空间、I/O设备以及建立相应的数据结构PCB。

2)撤销进程。系统在撤销进程时必须先对这些资源进行回收操作,然后再撤销PCB。

3)进程切换。在对进程进行切换时,由于要保留当前进程的处理器环境和设置新选中进程的处理器环境,为此需花费不少处理器时间

总而言之,由于进程是一个资源拥有者,因而在进程的创建、撤销和切换中,系统必须为之付出较大的时空开销。也正因为如此,在系统中所设置的进程数目不宜过多,进程切换的频率也不宜过高,但这也就限制了并发程度的进一步提高。

如何能使多个程序更好地并发执行,同时又尽量减少系统的开销,已成为近年来设计操作系统时所追求的重要目标。于是,有不少操作系统的开发者想到,可否将进程的上述两个属性分开,由操作系统分开进行处理。即如果作为调度和分派的基本单位,则不同时作为独立分配资源的单位,以使之轻装运行;而对拥有资源的基本单位,又不频繁地对之进行切换。正是在这种思想的指导下,产生了线程的概念。

1.什么是线程

在引入线程的操作系统中,线程是进程中的一个实体,是处理器调度和分派的基本单位。线程自己基本上不拥有系统资源,只拥有少量在运行中必不可少的资源(如程序计数器、一组寄存器和栈等),但它可与同属一个进程的其他线程共享进程所拥有的全部资源。

一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行由于线程之间的相互制约,致使线程在运行中也呈现出间断性。相应地,线程也同样有就绪、等待和运行三种基本状态。有的系统中线程还有终止状态等

2.线程的属性

线程有如下的一些属性。

1)每个线程有一个唯一的标识符和一张线程描述表,线程描述表记录了线程执行的寄存器以及栈等现场状态。

2)不同的线程可以执行相同的程序,即同一个服务程序被不同用户调用时操作系统为它们创建不同的线程。

3)同一个进程中的各个线程共享该进程的内存地址空间

4)线程是处理器的独立调度单位,多个线程是可以并发执行的。在单处理器的计算机系统中,各线程可交替地占用处理器;在多处理器的计算机系统中,各线程可同时占用不同的处理器;若各个处理器同时为一个进程内的各线程服务,则可缩短进程的处理时间。

5)一个线程在被创建后便开始了它的生命周期,直至终止;线程在生命周期内会经历等待状态、就绪态和运行态等各种状态变化。

3.引入线程的好处

1)创建一个新线程花费时间少(结束亦如此)。创建线程不需另行分配资源,因而创建线程的速度比创建进程的速度快,且系统的开销也少。

2)线程之间的切换花费时间少

3)由于同一个进程内的线程共享内存和文件,所以线程之间相互通信无须调用内核,故不需要额外的通信机制,使通信更简便,信息传送速度也快

4)线程能独立执行,能充分利用和发挥处理器与外部设备并行工作能力。

二、进程和线程

线程具有许多传统进程所具有的特征,故又称为轻量级进程( Light-Weight Process)或进程元;而把传统的进程称为重量级进程( Heavy- Weight Process),它相当于只有一个线程的任务。在引入了线程的操作系统中,通常一个进程都有若干个线程,至少也需要有一个线程。下面,我们从调度、并发性、系统开销、拥有资源等主要方面来对线程和进程进行比较。

1.调度

在传统的操作系统中,拥有资源的基本单位和独立调度、分派的基本单位都是进程。而在引入线程的操作系统中,则把线程作为调度和分派的基本单位,把进程作为资源拥有的基本单位,从而使传统进程的两个属性分开。分开的结果是,线程能够轻装运行,从而显著地提高系统的并发程度。在同一个进程中,线程的切换不会引起进程切换;相反,在由一个进程中的线程切换到另一进程中的线程时,将会引起进程切换。

2.并发性

在引人线程的操作系统中,不仅进程之间可以并发执行,而且在一个进程中的多个线程之间也可以并发执行,因而使操作系统具有更好的并发性,从而能更有效地使用系统资源和提高系统的吞吐量。

例如,在一个未引入线程的单处理器操作系统中,若仅仅设置一个文件服务进程,当它由于某种原因被封锁时,便没有其他的文件服务进程来提供服务。而在引入了线程的操作系统中,可以在一个文件服务进程中设置多个服务线程。当第一个线程等待时,文件服务进程中的第二个线程可以继续运行;当第二个线程封锁时,第三个线程可以继续执行。可见,线程可以显著地提高文件服务的质量以及系统的吞吐量

3.拥有资源

不论是传统的操作系统,还是有线程的操作系统,进程都是拥有资源的一个独立单位

一般而言,线程不拥有自己的系统资源(实际上也拥有少量必不可少的资源),但它可以访问其隶属进程的资源,比如一个进程的代码段、数据段以及相关的系统资源(如已打开的文件、分配使用的I/O设备等)。总之,一个进程中的资源可供它属下的所有线程共享。

4.系统开销

由于在创建或撤销进程时,系统都要为进程分配或回收资源,如内存空间、I/O设备等。因此,从整体上看,操作系统所付出的开销将显著地大于在创建或撤销线程时的开销。

类似地,在进行进程切换时,涉及整个当前进程处理器环境的保存以及新被调度运行的进程的处理器环境的设置,这些工作都需要占用或消耗系统的资源。而线程切换只需保存和设置少量寄存器的内容,并不涉及存储器管理方面的操作。可见,进程切换的开销也远大于线程切换的开销。此外,由于同一进程中的多个线程具有相同的地址空间,致使它们之间的同步和通信的实现也变得比较容易。在有的系统中,线程的切换、同步和通信都无须操作系统内核的干预。

三、线程实现机制

线程已在许多系统中实现,但实现的方式并不完全相同。

1.用户级线程

第一种实现方式是用户级线程(User- Level threads),这种线程不依赖于内核

用户级线程只存在于用户态中,对它的创建、撤销和切换不会通过系统调用来实现,因而这种线程与内核无关。相应地,内核也并不知道有用户级线程的存在,从内核角度考虑,就是按正常的方式管理,即单线程进程。

这种方法最明显的优点是,用户级线程包可以在不支持线程的操作系统上实现。过去所有的操作系统都属于这个范围,即使现在也有一些操作系统还是不支持线程。通过这一方法,可以用函数库实现线程。

这类实现都有同样的通用结构,如图3-7a所示。线程在一个运行时系统的顶部运行,这个运行时系统是一个管理线程的过程的集合,如线程创建、退出、等待等。

在用户空间管理线程时,每个进程需要有其专用的线程表( Thread Table),用来跟踪该进程中的线程。这些表和内核中的进程控制块类似,不过它仅仅记录各个线程的属性,如每个线程的程序计数器、堆栈指针、寄存器和状态等。该线程表由运行时系统管理。当一个线程转换到就绪状态或阻塞状态时,在该线程表中存放重新启动该线程所需的信息,与内核在进程表中存储进程的信息完全一样。

当某个线程做了一些会引起在本地被阻塞的事情之后,例如,等待进程中另一个线程完成某项工作,它调用一个运行时系统的过程,这个过程检查该线程是否必须进入阻塞状态。

如果是,它在线程表中保存该线程的寄存器(即它本身的),查看表中可运行的就绪线程,并把新线程的保存值重新装入机器的寄存器中。只要堆栈指针和程序计数器一被切换,新的线程就又自动投入运行。如果机器有一条保存所有寄存器的指令和另一条装入全部寄存器的指令,那么整个线程的切换可以在几条指令内完成。进行类似于这样的线程切换至少比陷入内核要快(或许更多)一个数量级,这是使用用户级线程包的优点。

在线程完成运行时,运行时系统可以把该线程的信息保存在线程表中,进而,它可以调用线程调度程序来选择另一个要运行的线程。保存该线程状态的过程和调度程序都只是本地过程,所以启动它们比进行内核调用效率更高。另一方面,不需要陷入,不需要上下文切换,也不需要对内存高速缓存进行刷新,等等,这就使得线程调度非常快捷。

用户级线程还有另一个优点。它允许每个进程有自己定制的调度算法。

2.内核级线程

第二种实现方式是内核级线程( Kernel-Supported Threads),这种线程依赖于内核

内核级线程依赖于内核,即无论是在用户进程中的线程,还是系统进程中的线程,它们的创建、撤销和切换都由内核实现。在内核中保留了一个线程控制块,系统根据该控制垬而感知该线程的存在并对线程进行控制。

如图3-7b所示,此时不再需要运行系统了,每个进程中也没有线程表。相反,在内核中有用来记录系统中所有线程的线程表。当某个线程希望创建一个新线程或撤销一个已有线程时,它进行一个系统调用,这个系统调用通过对线程表的更新就完成线程创建或撤销工作。

内核的线程表保存了每个线程的寄存器、状态和其他信息。这些信息和在用户空间中(在运行时系统中)的线程是一样的,但是现在保存在内核中。这些信息是传统内核所维护的每个单线程进程信息(即进程状态)的子集。另外,内核还维护了传统的进程表,以便跟踪进程的状态。

所有能够阻塞线程的调用都以系统调用的形式实现,这与运行时系统过程相比,代价很大。当一个线程阻塞时,内核可以选择运行同一个进程中的另一个线程(若有一个就绪线程)或者运行另一个进程中的线程。而在用户级线程中,运行时系统始终运行自己进程中的线程,直到内核剥夺它使用的处理器(或者没有可运行的线程存在了)为止。

内核线程不需要任何新的、非阻塞系统调用。另外,如果某个进程中的线程引起了页面失效,内核可以很方便地检査该进程是否有任何其他可运行的线程,如果有,在等待所需要的页面从磁盘读入时,就选择一个可运行的线程运行。这样做的主要缺点是系统调用的代价比较大,所以如果线程的操作(创建、终止等)比较多,就会带来很大的开销。

下面我们从几个方面对用户级线程和内核级线程进行比较。

(1)线程的调度与切换速度

核心级线程的调度和切换与进程的调度和切换十分相似。例如,在线程调度时的调度方式,同样也是采用抢占方式和非抢占方式两种。在线程的调度算法上,也同样可采用时间片轮转法、优先权算法等。当线程调度选中一个线程后,再将处理器分配给它。当然,线程在调度和切换上所花费的开销要比进程小得多。用户级线程的切换通常是发生在一个应用进程的诸线程之间,这时,不仅无须通过中断进入操作系统的内核,而且切换的规则也远比进程调度和切换的规则来得简单。例如,当一个线程封锁后会自动切换到下一个具有相同功能的线程。因此,用户级线程的切换速度特别快。

(2)系统调用

当传统的用户进程调用一个系统调用时,要由用户状态转入核心状态,用户进程将被封锁。

当内核完成系统调用而返回时,才将该进程唤醒,继续执行。而在用户级线程调用一个系统调用时,由于内核并不知道有该用户级线程的存在,因而把系统调用看作是整个进程的行为,于是使该进程等待,而调度另一个进程执行。同样是在内核完成系统调用而返回的,进程才能继续执行。如果系统中设置的是内核支持线程,则调度是以线程为单位。当一个线程调用一个系统调用时,内核把系统调用只看作是该线程的行为,因而封锁该线程,于是可以再调度该进程中的其他线程执行。

(3)线程执行时间

对于只设置了用户级线程的系统,调度是以进程为单位进行的。在采用轮转调度算法时,各个进程轮流执行一个时间片,这对诸进程而言似乎是公平的。但假如在进程A中包含了一个用户级线程,而在另一个进程B中含有100个线程,这样,进程A中线程的运行时间,将是进程B中各线程运行时间的100倍;相应地,进程A的运行速度比进程B的运行速度快100倍。假如系统中设置的是核心级线程,其调度是以线程为单位进行的,这样进程B可以获得的处理器时间是进程A的100倍,进程B可使100个系统调用并发工作。

3.混合实现方式

有一些系统同时实现了用户级线程和内核级线程。

人们已经研究了各种试图将用户级线程的优点和内核级线程的优点结合起来的方法种方法是使用内核级线程,然后将用户级线程与某些或者全部内核线程多路复用起来。如果采用这种方法,编程人员可以决定有多少个内核级线程和多少个用户级线程彼此多路复用。

这一模型带来最大的灵活度。

采用这种方法,内核只识别内核级线程,并对其进行调度。其中一些内核级线程会被多个用户级线程多路复用。如同在没有多线程能力操作系统中某个进程中的用户级线程一样,可以创建、撤销和调度这些用户级线程。在这种模型中,每个内核级线程都有一个可以轮流使用的用户级线程集合。

点赞