基本概念
我们说引入进程的目的是更好的使用多道程序并发执行,提高资源的利用率和系统吞吐量;而引入线程的目的则是减小程序在并发执行的时候所付出的时间开销,提高操作系统的并发性能。
线程可以理解成"轻量级进程",它是CPU的基本执行单元(进程是操作系统的资源分配的基本单元)也是程序执行的最小单元。线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源,但是它可以与同属一个进程的其他线程共享进程所拥有的全部资源。一个线程可以创建或撤销另一个线程,同时一个进程间的多个线程可以并发执行(与进程十分相似)。线程也有就绪态,阻塞态和运行态这三种状态。
引入线程的概念之后,进程就只作为除了CPU以外的系统资源的分配单元(CPU会被分配给线程而不是进程),而线程则作为处理机的分配单元。由于一个进程内有多个线程,如果线程的切换发生在同一个进程里面,就只需要很少的时间开销(因为上下文保存的东西比进程少多了)。
与进程的比较
1.调度:
传统的操作系统中(就是没有线程的操作系统中),每次调度都需要进行上下文切换,开销非常大。而在同一个进程中,线程是独立调度的基本单位,而线程的切换代价远低于进程,因为在同一进程中,线程之间的切换不会引起进程之间的切换。
2.并发性:
在引入线程的操作系统中,不仅进程之间可以并发执行,一个进程的多个线程之间也可以并发执行,不同进程之间的不同线程也能并发执行。从而使操作系统具有更好的并发性,提高了系统资源的利用率和系统的吞吐量
3.拥有资源:
进程使系统中拥有资源的基本单位,而线程不拥有系统资源(只拥有一点必不可少、能保证独立运行的资源,比如说CPU时间片),但是线程可以访问其隶属进程的系统资源。
4.独立性:
每个进程都拥有独立的地址空间和资源,除了共享全局变量,不允许其他进程访问。某进程中的线程对其他进程不可见。同一进程中的不同线程是为了提高并发性以及进行相互之间的合作而创建的,他们共享进程的地址空间和资源。
5.系统开销:
在创建或撤销进程的时候,系统都要为之分配或是回收进程控制块PCB以及其它资源,开销很大。而线程创建和撤销的时候需要管的只是一小部分的寄存器的内容,开销很小。此外,由于同一进程的多个线程共享进程的地址空间,因此这些线程之间的同步很容易实现,甚至不需要操作系统的干预。
6.支持多处理机系统:
对于传统单线程进程,不管有多少处理机,进程只能运行在一个处理机上面。而对于多线程的进程,可以把一个进程里面的不同线程分配给不同的处理机上面执行。
线程的属性
多线程操作系统中的进程以及不再是一个基本的执行实体,但是它仍然具有执行相关的状态。所谓的进程处于执行状态,也可以理解为这个进程内的某个线程正在执行。线程的主要属性如下:
-
线程是一个轻型实体,它不拥有资源,但每个线程都拥有一个唯一的标识符和一个线程控制块(进程也有一个唯一的标识符和进程控制块(PCB))线程控制块记录了线程执行的寄存器和栈等现场状态
-
不同的线程可以执行相同的程序,即同一个服务程序被不同的用户调用的时候,操作系统把他们创建成不同的线程
-
同一进程中的不同线程共享这个进程的所有资源
-
线程是处理机的独立调度单位,多个线程是可以并发执行的。在单CPU的计算机系统中,各个线程可以交替地占用CPU;在多CPU的计算机系统中,各线程可同时占用不同的CPU,如果各个CPU同时为一个进程的各线程服务,就可以缩短进程的处理时间
-
一个线程被创建后,便开始了它的生命周期,直到线程终止。线程在生命周期内会经历阻塞态,就绪态和运行态等各种状态的变化
线程的状态和状态切换
与进程十分相似,进程在运行的时候有三种基本状态,而线程也一样:
-
执行状态:线程已获得处理机而正在运行
-
就绪状态:线程已经具备各种执行条件,只需要再获得CPU便可以立即执行
-
阻塞状态:线程在执行中因某个事件受阻而处于暂停状态
线程这三种基本状态之间的转换和进程的基本状态之间的转换是一样的
线程的组织和控制
1.线程控制块:
与进程类似,系统也会给每个线程分配一个线程控制块TCB,用于记录控制和管理线程的信息。线程控制块通常包括:
-
线程标识符
-
一组寄存器
-
线程运行状态,用于描述线程正处于何种状态
-
优先级
-
线程专有存储区,线程切换的时候用于保存现场(线程上下文)
-
堆栈指针,用于过程调用时保存局部变量和返回地址等
同一进程的所有线程都完全共享进程的地址空间和全局变量。各个线程都可以访问进程地址空间中的每个单元,所以讲一个线程可以读、写甚至清楚另一个线程的堆栈
2.线程的创建:
线程也是有生命周期的,它由创建而产生,调度而执行,由终止而消亡。相应的,在操作系统中就有用于创建线程和终止线程的函数。
用户程序启动的时候,通常只有一个称为"初始化线程"的线程正在执行,这个线程的主要功能是创建新线程。等线程创建函数执行完之后,会返回一个线程标识符。
3.线程的终止:
当线程完成自己的任务之后,或者是出现了异常而需要强制终止的时候,由终止进程调用相应的函数执行种植操作。但有些线程一旦被建立,就会一直运行下去而不会终止。通常,线程被终止之后不会立即释放它所占有的资源,只有当进程中的其它线程执行了分离函数之后,被终止进程才会与资源分离,这个时候的资源才会被其它的线程利用。
被终止单还没有释放资源的线程仍然可以被其它的线程调用,被调用之后,这个线程会重新恢复运行。
线程的实现方式
线程的实现方式分为两类:用户级线程(ULT)和内核级线程(KLT),其中内核级线程又被称为内核支持的线程。
1.用户级线程(ULT):
用户级线程中,有关线程管理(创建,撤销)的所有工作都由应用程序在用户空间中完成,线程对于内核来说是透明的,内核不知道线程的存在,只看得到进程。应用程序可以通过使用线程库设计成多线程程序。通常,应用程序从单线程开始,在这个线程中开始运行,在运行中的任何时刻,可以通过调用线程库中的派生例程创建一个在相同进程中运行的新线程。
对于设置了用户级线程的系统,其调度仍然是以进程为单位的(毕竟内核看不到你的线程),各个进程轮流执行一个时间片。
优点:
-
线程之间的切换不需要转换到内核空间,减少开销
-
调度算法可以是进程专用的,不同的进程可以根据自己的需要,对自己的线程选择不同的调度算法
-
用户及线程的实现与操作系统平台无关,对线程管理的代码库是属于用户程序的一部分
缺点:
-
系统效用的阻塞问题,当线程执行一个系统条用被阻塞的时候,整个进程都被阻塞了,这个进程里面的其它线程是不能去执行的
-
不能发挥多处理机优势,毕竟内核又看不到你线程,咋让多个处理机处理同一个进程内的多个线程?
2.内核级线程(KLT):
操作系统中,无论是系统进程还是用户进程,都是在系统内核的支持下运行的,与内核紧密相关。内核级线程同样也是在内核的支持下运行的,线程管理的所有工作也是在内核空间内实现的。内核空间也为每个内核级线程设置一个线程控制块,内核根据控制块感知某线程的存在,并对线程加以控制。
优点:
-
能发挥多处理机的优势。多个处理机能够同时处理一个进程的不同线程
-
如果进程中的一个线程被阻塞了,还有其它的线程可以被执行
-
内核支持县城具有很小的数据结构和堆栈,线程切换比较快
-
内核本身也可以采用多线程技术,可以提高系统的执行速度和效率
缺点:
-
同一进程中的线程切换,需要从用户态转到核心态进行系统开销较大。这是因为用户进程的线程在用户态进行,而线程调度是在核心态实现的。
3.组合方式:
把上述两个方法结合起来,内核支持多个内核级线程的建立、调度和管理,同时也允许用户程序建立、调度和管理用户级线程。一些内核级线程对应多个用户级线程,这是用户级线程通过时分多路复用内核级线程实现的。同一进程中的多个线程也可以同时在多处理机上面并行执行,且在一个线程阻塞的时候不会导致整个进程阻塞。
线程实现方式的介绍中,提到了通过线程库来创建和管理线程。线程库是为程序员提供创建和管理线程的API。实现线程库的主要方法为以下两种:
-
在用户空间中提供一个没有内核支持的库。这个库的所有代码和数据结构都位于用户空间中,这意味着,调用库内的一个函数只导致用户空间中的一个本地函数的调用
-
实现由操作系统直接支持的内核级的一个库。对于这种情况,库内的代码和数据结构都在内核空间内。调用库中的API通常会导致对内核的系统调用。
多线程模型
有些系统同时支持用户级线程和内核级线程,由于用户级线程和内核级线程的连接方式的不同,从而形成了下面三种不同的多线程模型:
1.多对一模型:
讲多个用户级线程映射到一个内核级线程。这些用户级线程一般属于一个进程,线程的调度和管理在用户空间完成。只有当用户线程访问内核的时候,才将其映射到一个内核级线程上,但是每次只允许一个线程进行映射。
优点:线程管理在用户空间中进行,效率比较高
缺点:如果一个线程发生了阻塞,整个进程都会被阻塞;不支持多处理机的分布式运行
2.一对一模型:
将每个用户级线程映射到一个内核级线程
优点:当一个线程被阻塞之后,允许调度另一个线程运行
缺点:每创建一个用户级线程,就要创建一个内核级线程,开销较大
3.多对多模型:
将n个用户级线程映射到m个内核级线程上,要求n≥m。
克服了多对一模型并发度不高的缺点,又克服了一对一模型的一个用户进程占用太多内核级线程而开销太大的缺点。此外,还拥有上面两个模型各自的优点。