本文共 4528 字,大约阅读时间需要 15 分钟。
(1)线程,有时被称为轻量级进程,是程序执行流的最小单元。一个标准的线程由线程ID,当前指令指针,寄存器集合和堆栈组成。另外,线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。
(2)进程(Process)定义了一个执行环境,包括它自己私有的地址空间、一个句柄表,以及一个安全环境;线程(thread)则是一个控制流,有自己的调用栈(call stack),记录了执行的历史。每个进程都包含一个或多个线程,当进程被初始化创建时系统为该进程创建第一个线程;当最后一个线程结束时,进程也随之结束。
说道这里就需要说到线程的工作模式:
进程中的线程既可以在用户模式下运行,也可以在内核模式下运行。如果一个进程运行在用户模式下,那么它就只能访问进程地址空间;如果运行在内核模式下,那么它将可以访问这个地址空间。
用户级线程 应用程序在操作系统提供的单个控制流的基础上,通过在有些控制点(比如系统调用)上分离出一些虚拟的控制流,从而模拟多个控制流的行为。由于应用程序对指令流的控制能力相对较弱,所以,用户级线程之间的切换往往受线程本身行为以及线程控制点选择的影响,线程是否能够公平地获得处理器时间取决于这些线程的代码特征。而且,支持用户级线程的应用程序代码很难做到跨平台移植,以及对于多线程模型的透明。用户级线程模型的优势: (1)线程切换效率高,因为它不涉及系统内核模式和用户模式之间的切换; (2)另外一个好处是应用程序可以采用适合自己特点的线程选择算法,可以根据应用程序的逻辑来定义线程的优先级,当线程数量很大时。但是,相对的会增加应用程序代码的复杂度。
内核级线程 指操作系统提供的线程语义,由于操作系统对指令流有完全的控制能力,甚至可以通过硬件终端来强迫一个进程或是线程暂停执行,以便把处理器时间移交给其他的进程或是线程,所以,内核级线程有可能应用各种算法来分配处理器时间。线程可以有优先级,高优先级的线程被优化执行,他们可以抢占正在执行的低优先级线程。在支持线程语义的操作系统中,处理器的时间通常是按照线程而非进城来分配的,因此,系统有必要维护一个全局的线程表,在线程表中记录每个线程的寄存器、状态以及其他的一些信息。然后,系统在适当的时候挂起一个正在执行的线程,选择一个新的线程在当前处理器上继续执行。 内核级线程的好处: 应用程序无需考虑是够要在适当的时候把控制权交给其他的线程,不比担心自己霸占处理器而导致其他线程得不到处理器时间。因而应用程序只要按照正常的指令来实现自己的逻辑就可以了,内核会妥善处理好线程之间的共享处理器的资源分配问题。但是这样带来的代价便是需要在用户模式和内核模式下进行切换:从用户模式切换到内核模式,再从内核模式切换到用户模式。在Intel的处理器上可能需要几百上千和处理器指令周期。
对于线程管理和调度,从笼统来说是这样的:在Windows的内核结构中,进程和线程的核心机制是在微内核中实现的,而管理机制是在执行体中实现的。这是基于机制与策略分工的原则。例如,线程调度是由微内核来完成,而线程和进程的创建、各种管理属性的设置则是由执行体来完成的。
既然线程作为程序执行流的最小单元,那么在Windows中,对于线程的管理和调度线程是这样的:在Windows NT以后,Windows实现了一个基于优先级的抢先式多处理器调度系统。调度系统总是运行优先级最高的就绪线程。通常线程可在任何可用处理器上运行,但也可限制某线程只能在某处理器上运行。先看一下优先级是怎么划分的。线程总共分为32个优先级,具体看下图:
Windows实现了基于优先级的抢占式(Preemptive)线程调度算法,每个线程都有一个基本优先级(base priority)和一个动态优先级(priority)。优先级的值处于0~31之间,共分为三个类别: (1)0级表示系统优先级,为最低优先级,仅用于零页面线程 (2)1~15为动态优先级,在某些情况下线程的动态优先级可以在此范围内进行微调,例如当一个前台线程从等待状态被唤醒时,期优先级将有一点点提升,从而可以尽快获得处理器的执行权(至少比相同基本优先级的其他线程优先) (3)16~31为实时优先级,用于处理一些实时事务
线程的基本优先级=[进程的基本优先级 - 2,进程的基本优先级 + 2],由应用程序控制。线程的动态优先级 = [ 进程的基本优先级 - 2, 31],由NT 核心控制。为了提高调度速度,Windows NT维护了一个称为就绪位图的32位量。就绪位图中的每一位指示一个调度优先级的就绪队列中是否有线程等待运行。还有一个称为空闲位图的32位量用来指示一个处理机是否处于空闲状态。
先来看看三个典型的线程调度算法: (1)先到服务算法。在非抢占式系统中,这一算法比较自然,简单来讲,用一个FIFO队列就可以满足。所有的线程构成一个队列,最先进入对嘞的线程获得处理器的执行权,得到放弃处理器的执行权时,又回到队列的队尾,下一个线程继续执行。若是在这个过程中有新的线程加入,将其添加到队尾。 (2)时间片轮转调度算法。处理器的时间被分为最大长度不超过牟哥值的时间片段,成为时间片,然后用轮转方法分配给每个线程。当一个线程获得了处理器的执行权之后,按照自身的逻辑执行下去,知道时间片用完,或者自己主动放弃执行权(比如要等待一个信号量)。系统在获得处理器的控制权以后,用轮转的方式找到下一个正在等待运行的线程,让它继续执行。 (3)优先级调度算法。在时间片轮转算法中,一个基本的假设是所有的线程都同等重要,这一假设在专用计算机上可能是非常合理的,但是,在现代多用途计算机上,可能难以胜任多种不同类型的应用程序并发执行的实际情形。优先级调度算法是这种算法的一个改进,基本思路是,每个线程都有一个优先级值,高优先级的线程总是优先被考虑在处理器上执行。操作系统在管理线程时,可以使用一个优先级队列,或者每个优先级用一个队列存放所有满足执行条件的线程。
对于上面提到的优先级机制虽然可以实现调度,但光有线程的优先级的话那么相同优先级的线程中只有一个执行到底,其他的都处于等待,所以还有一个时间配额的制度。时间配额是一个线程从进入运行状态到系统检查是否有其他优先级相同的线程需要开始运行之间的时间总和。一个线程用完了自己的时间配额时,如果没有其它相同优先级线程,Windows 2000将重新给该线程分配一个新的时间配额,并继续运行。每个线程都有一个代表本次运行最大时间长度的时间配额。
在每个线程拥有优先级和时间配额后,通常线程先在许多等待函数的调用中进入等待状态,进入等待状态线程的时间配额不会被重置,而是在等待事件出现时,线程的时间配额被减1相当于1/3个时钟间隔;如果线程的优先级大于等于14在等待事件出现时,线程的优先级被重置。可能在这两种情况下出现抢先的情况:一个是高优先级线程的等待完成,即一个线程等待的事件出现。 一个是一个线程的优先级被增加或减少。 在判断一个线程是否被抢先时,并不考虑线程处于用户态还是内核态,调度器只是依据线程优先级进行判断。 当线程被抢先时,它被放回相应优先级的就绪队列的队首。 如果刚用完时间配额的线程优先级降低了,系统将寻找一个优先级高于刚用完时间配额线程的新设置值的就绪线程。如果刚用完时间配额的线程的优先级没有降低,并且有其他优先级相同的就绪线程,系统将选择相同优先级的就绪队列中的下一个线程进入运行状态,刚用完时间配额的线程被排到就绪队列的队尾(即分配一个新的时间配额并把线程状态从运行状态改为就绪状态)。 如果没有优先级相同的就绪线程可运行,刚用完时间配额的线程将得到一个新的时间配额并继续运行。 当线程完成运行时,它的状态从运行状态转到终止状态。线程完成运行的原因可能是通过调用函数而从主函数中返回或通过被其他线程通过调用函数来终止。如果处于终止状态的线程对象上没有未关闭的句柄,则该线程将被从进程的线程列表中删除,相关数据结构将被释放。
对于线程的运行状态:每个线程从初始化开始到最后终止,其状态会随着系统的状态以及它自身的代码逻辑而发生变化。Windows调度算法的线程状态转移图比传统的抢占式调度算法略微细致一些。本质上每个线程都处于两种状态之一:
(1)满足继续执行的条件,整在排队或者已经在执行。在这一情况中,线程按照优先级排队执行;对于多处理器系统,排队的过程要复杂些,调度器步进要处理多个队列,而且要考虑每个处理器的就绪线程队列的平衡程度。由于各个线程可能存在处理器亲和性,所以,此排队和分发过程要略微复杂一些
(2)不满足继续执行的条件,处于等待状态,或者它的调用栈甚至所处的进程被换出内存。在这一情况,对于不满足执行条件的情形,长期处于等待状态的线程,其调用栈(设置整个进程)可能被换出内存,在这种情况下,一旦执行条件已满足,则要首先被换回内存,然后才能参与排队分发
归结我们得到大概的Windows线程基本调度机制是:线程优先级制度+时间配额制度,来管理实现的
线程转让CPU处理时间是被迫的。占先式多任务可以防止线程独占CPU,允许其它线程公平地分享CPU执行时间,这和16位Windows环境下的协作式多任务有着本质的区别。在16位Windows环境下,如果一个程序进入无限循环,则其它应用程序可能永远没有机会执行;而在Windows NT环境下这种情况不会发生,相反,许多线程的执行部分都采用了循环扫描的结构。这也是实现了多线程的基础。