注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

写着玩

Bob

 
 
 

日志

 
 
 
 

线程学习(3)线程优先级和调度  

2009-08-02 01:21:28|  分类: Win32 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

终于开始写到3了,最近一直很忙,似乎还有点累。上班时间是不可能有空写的,只有回到家等女儿睡着了才能坐下来。每每这个时候也不早了,1:23,现在。后面还有好多,还得加油。

        说到线程的调度,我觉得还是得从线程的优先级讲起。在Windows中,每个线程都被赋予了优先级的概念,线程总共有32个优先级数从0(最低)到31(最高)。从前面的章节我们知道,线程是从属于某个进程的,那么对于某个具体进程的某个线程的优先级是怎么确定的呢?这得从几个优先级概念说起。一个线程的优先级是由进程的基本优先级(或者叫优先级类)和线程的相对优先级决定的。因为我们无法直接设置一个线程的优先级数,比如2或29。
        进程的基本优先级:他是通过调用系统API---SetPriorityClass来设置,运行中的进程的优先级可以通过任务管理器查看。基本优先级是针对进程来讲的,基本优先级有8个类别,分别是:ABOVE_NORMAL_PRIORITY_CLASS,
BELOW_NORMAL_PRIORITY_CLASS,
HIGH_PRIORITY_CLASS,IDLE_PRIORITY_CLASS,
NORMAL_PRIORITY_CLASS,
PROCESS_MODE_BACKGROUND_BEGIN,
PROCESS_MODE_BACKGROUND_END,
REALTIME_PRIORITY_CLASS.
具体的含义可以查看MSDN。如果程序没有进行特别设置,那么进程的基本优先级将会设置为NORMAL_PRIORITY_CLASS这个级别。先对来说,使用高于正常的优先级需要特别小心,特别是REALTIME_PRIORITY_CLASS.他表示进程中线程必须立即对事件作出响应,以便执行关键时间的任务.该进程中的线程还会抢先于操作系统组件之前运行.使用本优先级类时必须极端小心.
线程相对优先级:相对优先级是针对线程的,他是通过调用SetThreadPriority函数设置的。具体参数可以参考MSDN.那么线程的优先级到底是怎么确定的呢?基本优先级和相对优先级如何结合的呢?下表就是他们的对应关系,是从MSDN上copy过来的。不过M$也说过他们不承诺任何关于优先级级数的保证。因为这个未来可能会变的。所以写程序千万不要直接依赖于有限级数。
具体的含义可以查看MSDN。如果程序没有进行特别设置,那么进程的基本优先级将会设置为NORMAL_PRIORITY_CLASS这个级别。先对来说,使用高于正常的优先级需要特别小心,特别是REALTIME_PRIORITY_CLASS.他表示进程中线程必须立即对事件作出响应,以便执行关键时间的任务.该进程中的线程还会抢先于操作系统组件之前运行.使用本优先级类时必须极端小心.
线程相对优先级:相对优先级是针对线程的,他是通过调用SetThreadPriority函数设置的。具体参数可以参考MSDN.那么线程的优先级到底是怎么确定的呢?基本优先级和相对优先级如何结合的呢?下表就是他们的对应关系,是从MSDN上copy过来的。不过M$也说过他们不承诺任何关于优先级级数的保证。因为这个未来可能会变的。所以写程序千万不要直接依赖于有限级数。

Process priority class Thread priority level Base priority
IDLE_PRIORITY_CLASS THREAD_PRIORITY_IDLE 1
THREAD_PRIORITY_LOWEST 2
THREAD_PRIORITY_BELOW_NORMAL 3
THREAD_PRIORITY_NORMAL 4
THREAD_PRIORITY_ABOVE_NORMAL 5
THREAD_PRIORITY_HIGHEST 6
THREAD_PRIORITY_TIME_CRITICAL 15
BELOW_NORMAL_PRIORITY_CLASS THREAD_PRIORITY_IDLE 1
THREAD_PRIORITY_LOWEST 4
THREAD_PRIORITY_BELOW_NORMAL 5
THREAD_PRIORITY_NORMAL 6
THREAD_PRIORITY_ABOVE_NORMAL 7
THREAD_PRIORITY_HIGHEST 8
THREAD_PRIORITY_TIME_CRITICAL 15
NORMAL_PRIORITY_CLASS THREAD_PRIORITY_IDLE 1
THREAD_PRIORITY_LOWEST 6
THREAD_PRIORITY_BELOW_NORMAL 7
THREAD_PRIORITY_NORMAL 8
THREAD_PRIORITY_ABOVE_NORMAL 9
THREAD_PRIORITY_HIGHEST 10
THREAD_PRIORITY_TIME_CRITICAL 15
ABOVE_NORMAL_PRIORITY_CLASS THREAD_PRIORITY_IDLE 1
THREAD_PRIORITY_LOWEST 8
THREAD_PRIORITY_BELOW_NORMAL 9
THREAD_PRIORITY_NORMAL 10
THREAD_PRIORITY_ABOVE_NORMAL 11
THREAD_PRIORITY_HIGHEST 12
THREAD_PRIORITY_TIME_CRITICAL 15
HIGH_PRIORITY_CLASS THREAD_PRIORITY_IDLE 1
THREAD_PRIORITY_LOWEST 11
THREAD_PRIORITY_BELOW_NORMAL 12
THREAD_PRIORITY_NORMAL 13
THREAD_PRIORITY_ABOVE_NORMAL 14
THREAD_PRIORITY_HIGHEST 15
THREAD_PRIORITY_TIME_CRITICAL 15
REALTIME_PRIORITY_CLASS THREAD_PRIORITY_IDLE 16
THREAD_PRIORITY_LOWEST 22
THREAD_PRIORITY_BELOW_NORMAL 23
THREAD_PRIORITY_NORMAL 24
THREAD_PRIORITY_ABOVE_NORMAL 25
THREAD_PRIORITY_HIGHEST 26
THREAD_PRIORITY_TIME_CRITICAL 31

比如两个优先级都是正常的级别,那么线程的优先级就是8。再如基本优先级是REALTIME_PRIORITY_CLASS ,|
相对优先级是THREAD_PRIORITY_TIME_CRITICAL,那么线程的优先级将是31。
       如果你够细心的话,你也许发现了一个问题,就是前面说到线程的优先级是从0到31的,但是从表中来看并没有那种组合的结果优先级是0.没错,真的没有0,确实是组合不错来的。因为当系统引导的时候,它会创建一个特殊的线程,称为0页线程.该线程被赋予优先级0,它是整个系统中唯一的一个在优先级0上运行的线程 。当系统中没有任何线程需要执行的时候,0页线程负责将系统中的所有空闲的RAM页面置0.除了0还有其他一些数字在上面表里也找不到,比如17,18,19,20,21,27,28,29,30都没有。17,18,19,20,21,27,28,29,30。如果编写一个以内核方式运行的设备驱动程序,可以获得这些优先级的等级,而用户方式的应用程序则不能.
        说到了线程优先级,不得不说一下硬件中断。我们知道不同的处理器的中断机制是不一样的,虽然中断控制器做了中断级别的优化工作,Windows还是强制使用自己的中断优先级方案,即IRQLs。内核在内部以数字表示IRQLs,x86上是从 0到31,x64和IA64上是从0到15,数字越高则中断的优先级别越高。内核为软件中断定义了一系列标准的IRQLs,HAL也将硬件诊断号映射到 IRQLs。
线程学习(3)线程调度、优先级和亲缘性 - yolcy - 写着玩
       中断是根据优先级被响应的,一个高优先级的中断会比一个低优先级的中断先被响应。当有一个高优先级的中断产生,处理器保存被中断线程的状态并呼叫与之相关的陷阱调度器(trap dispatchers)。陷阱调度器提升IRQL并调用该中断的服务例程。在中断例程结束之后,陷阱调度器降低处理器的IRQL到中断发生前的级别,接着载入之前存储的机器状态。被中断的线程从被中断的点重新开始执行。当内核降低IRQL后,之前被屏蔽的优先级较低的中断就可能被接收。如果顺利的话,内核会重复这一过程来处理新的中断。
   每个处理器的IRQL设置决定了该处理器可接收的中断。IRQLs也被用来同步访问内核模式的数据结构。当一个线程在内核模式下运行,它既可以直接调用 KeRaiseIrql和KeLowerIrql来提升或降低处理器的IRQL,也可以间接地通过调用函数获得内核同步物件以提升或降低处理器的 IRQL。从拥有高于当前IRQL的中断源发出的中断可以中断处理器,而从有着小于或等于当前IRQL的中断源发出的中断会被屏蔽,直到有执行线程降低该 IRQL。
       一个内核模式的线程提升或降低运行它的处理器的IRQL取决于它的任务。例如,如果一个中断发生,陷阱处理程序(或者是处理器)会提升处理器的IRQL到 为该中断源指定的IRQL。这个高度屏蔽了所有小于或等于该IRQL的中断(只作用于该处理器),保证了在处理器响应该中断的过程中不会被同级或级别较低 的中断打断。被屏蔽的中断要么被交由其它的处理器处理,要么就要等到IRQL降下来。因此,系统中的所有组件,包括内核和设备驱动都希望IRQL能够待在无源级别(也叫作低级别)。原因是如果IRQL不被长时间的置于较高的状态,设备驱动就可以及时地响应硬件中断。
       IRQL的优先级与线程调度的优先级完全不同。后者是线程的一个属性,而IRQL是中断源,比如键盘、鼠标的一个属性。此外,每个处理器有一个IRQL设置,该设置在执行系统代码时发生变化。所有的线程都运行在中断优先级0和1上。内核态的异步调用运行在1上,用户线程运行在0上,内核线程可以中断用户线程。而且只有内核线程才能提高自己的终端优先级。用户线程虽然提高优先级可以阻塞系统线程,但是用户线程优先级的提高并不会阻塞硬件中断。而线程调度代码运行在DPC/线程调度中断优先级(2级)。这样可防止调度器代码与线程在访问调度器数据结构时发生冲突。
        在了解了什么是优先级以及如何设置优先级和中断之后,我们来了解另外一个概念-----时间配额
        时间配额是一个线程从进入运行状态到Windows检查是否有其他优先级相同的线程需要开始运行之间的时间总和。每个线程都有一个代表本次运行最大时间长度的时间配额。时间配额不是一个时间长度值,而一个称为配额单位(quantum unit)的整数。 缺省时,在Windows 2000专业版中线程时间配额为6;而在Windows 2000服务器中线程时间配额为36。注册表项:HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\PriorityControl\Win32PrioritySeparation Win32PrioritySeparation 可以修改时间配额。
线程状态

        线程在不同的时间有不同的线程状态,在NT内核的windows中线程总共有7种状态:

线程状态

说明

Ready(就绪)

此状态下的线程正在等待执行,当调度程序需要找一个线程来执行时,它仅考虑就绪状态下的线程池。

Standby(备用)

已经被选中(当前活动线程的后继),当条件合适时,调度程序对这个线程执行一个上下文转换,备用线程将被切换到某个特定的处理器上运行。对于系统中的每一个处理器,只能有一个线程处于备用状态。

Running(运行)

一旦调度程序将环境切换到某个(备用)线程,这个线程就进入运行状态并开始执行。线程一直执行,直到内核将其抢占去运行一个更高优先级的线程,或者它的时间片到结束运行或自动进入等待状态。

Waiting(等待)

一个线程可能因为以下几个原因而进入等待状态:(1)自动等待一个对象以便同步它的执行。(2)操作系统可以代替该进程进入等待(如为了解决换页I/O)。(3)环境子系统引导线程挂起。

线程等待状态结束后,根据其优先级,开始执行,或者进入就绪状态。

Transition (转变)

当一个线程已经准备好执行,但它的内核栈被换出了内存,这时线程就进入转变状态。一旦它的内核栈被换入内存,线程就进入就绪状态。

Terminated (终止)

当一个线程完成执行,它就进入终止状态。终止后,线程对象可能被删除,也可能不被删除,这将取决于对象管理器什么时候删除对象的策略。如果执行体中有一个指针指向线程对象,执行体可以对线程对象重新初始化并再次使用它。

Initialized (初始)

当一个线程被创建时的状态。(内部使用)

下面这个图是各个状态之间的转换关系:

 

线程学习(3)线程调度、优先级和亲缘性 - yolcy - 写着玩

 

处理器的亲合性
        按照系统默认设置,当系统将CPU分配给线程时,如果其他因素都相同的话,那么系统将设法在线程上次运行的那个处理器上再次运行线程。这样是为了重复使用CPU的高速缓存。这就是系统的软亲合性。但是为了适应某些特殊情况,系统将允许用户设置线程或进程的亲合性。也就是允许哪个CPU能允许哪些线程。这就是处理器的硬亲合性。API为:SetProcessAffinityMask/SetThreadAffinityMask。
线程调度
        Windows是一个基于优先级的抢占式多处理器调度系统。调度系统总是运行优先级最高的就绪线程。Windows的没有单独的调度模块或程序,调度的代码是在内核中实现的,广泛分布在内核中那些与调度相关的事件发生的地方。这些负责调度的程序被总称为“内核的调度器”。线程调度发生在DPC/Dispatch级别。  当一个线程进入运行状态时,每次时钟中断都会从时间配额中减少一个固定值(一般是3)。当配额用完后,系统中断线程,看是否需要降低线程优先级,并查找是否有其他高优先级或相同优先级的线程在等待运行。因为windows是一个抢占式的操作系统,,因为一个线程可能会在自己时间配额还没用完的时候就被其他已经就绪的高优先级的线程给抢先了。而且用户线程可以抢先内核线程。抢先时只与优先级有关而不关心是用户线程还是内核线程。处于实时优先级的线程被抢先时,时间配额被重置为一个完整的时间片,而处于动态优先级的线程被抢先时,时间配额不变。当一个线程出现等待事件时,时间配额会被减1,当线程优先级大于14时,优先级被重置。有四种情况会引起线程的调度: 

  • 变成就绪状态的线程。例如:一个新创建的线程,或者从等待状态释放出来的线程。
  • 因其时间配额用完而离开运行状态的线程,它或者结束了,或者进入等待状态。
  • 线程的优先级改变了,是因为系统调用,或者是Windows自己改变了优先级。
  • 正在运行的线程的处理器亲合性改变了。

在每一个上述情况的衔接点,Windows必须决定下一个运行的线程是哪一个。一旦选择了一个新的线程运行,Windows将对其执行一个上下文转换的操作,即保存正在运行的线程的相关的机器状态,装载另一个线程的状态,开始新线程的执行。
动态提高优先级
        windows(2k/xp)在下面5种情况会自动提高线程的优先级:

  • I/O操作完成后的线程优先级提升
  •     1. 在完成I/O操作后,Windows 2000将临时提升等待该操作线程的优先级,以保证等待I/O操作的线程能有更多的机会立即开始处理得到的结果 
        2. 为了避免I/O操作导致对某些线程的不公平偏好,在I/O操作完成后唤醒等待线程时将把该线程的时间配额减1
        3.线程优先级的实际提升值是由设备驱动程序决定的。与I/O操作相关的线程优先级提升建议值在文件“Wdm.h”或“Ntddk.h”中。设备驱动程序在完成I/O请求时通过内核函数IoCompleteRequest来指定优先级提升的幅度。
        4. 线程优先级的提升幅度与I/O请求的响应时间要求是一致的,响应时间要求越高,优先级提升幅度越大

  • 等待事件和信号量后的线程优先级提升。
  •     1. 当一个等待执行事件对象或信号量对象的线程完成等待后,它的优先级将提升一个优先级。 
        2. 阻塞于事件或信号量的线程得到的处理机时间比处理机繁忙型线程要少,这种提升可减少这种不平衡带来的影响。
        3. SetEvent、PulseEvent或ReleaseSemaphore函数调用可导致事件对象或信号量对象等待的结束。
         4. 提升是以线程的基本优先级为基点的,而不是线程的当前优先级。提升后的优先级永远不会超过15。在等待结束时,线程的时间配额被减1,并在提升后的优先级上执行完剩余的时间配额;随后降低1个优先级,运行一个新的时间配额,直到优先级降低到初始的基本优先级。

  • 前台线程在等待结束后的优先级提升
         1. 对于前台进程中的线程,一个内核对象上的等待操作完成时,内核函数KiUnwaitThread会提升线程的当前优先级(不是线程的基本优先级),提升幅度为变量PsPrioritySeparation的值。

    2. 在前台应用完成它的等待操作时小幅提升它的优先级,以使它更有可能马上进入运行状态,有效改进前台应用的响应时间特征。

    3. 用户不能禁止这种优先级提升,甚至是在用户已利用Win32的函数SetThreadPriorityBoost禁止了其他的优先级提升策略时,也是如此。

  • 图形用户接口线程被唤醒后的优先级提升。
           1. 拥有窗口的线程在被窗口活动唤醒(如收到窗口消息)时将得到一个幅度为2的额外优先级提升。

     2. 窗口系统(Win32k.sys)在调用函数KeSetEvent时实施这种优先级提升,KeSetEvent函数调用设置一个事件,用于唤醒一个图形用户接口线程。
           3. 这种优先级提升的原因是改进交互应用的响应时间。

  • 对处理机饥饿线程的优先级提升
           1. 系统线程“平衡集管理器(balance set manager)” 会每秒钟检查一次就绪队列,是否存在一直在就绪队列中排队超过300个时钟中断间隔的线程。

     2. 如果找到这样的线程,平衡集管理器将把该线程的优先级提升到15,并分配给它一个长度为正常值两倍的时间配额;

    3. 当被提升线程用完它的时间配额后,该线程的优先级立即衰减到它原来的基本优先级。

  • Sleep 函数

    系统将在大约的指定毫秒数内使线程不可调度。Windows不是个实时操作系统。虽然线程可能在规定的时间被唤醒,但是它能否做到,取决于系统中还有什么操作正在进行。可以调用Sleep,并且为dwMilliseconds参数传递INFINITE。这将告诉系统永远不要调度该线程。这不是一件值得去做的事情。最好是让线程退出,并还原它的堆栈和内核对象。可以将0传递给Sleep。这将告诉系统,调用线程将释放剩余的时间片,并迫使系统调度另一个线程。但是,系统可以对刚刚调用Sleep的线程重新调度。如果不存在多个拥有相同优先级的可调度线程,就会出现这种情况。那么我们如何做到真正切换到另外一个线程呢?答案是可以调用SwitchtoThread函数。

    SwitchtoThread函数

    系统提供了SwitchToThread函数。当调用这个函数的时候,系统要查看是否存在一个迫切需要CPU时间的线程。如果没有线程迫切需要CPU时间,SwitchToThread就会立即返回。如果存在一个迫切需要CPU时间的线程,SwitchToThread就对该线程进行调度(该线程的优先级可能低于调用SwitchToThread的线程)。这个迫切需要CPU时间的线程可以运行一个时间段,然后系统调度程序照常运行。该函数允许一个需要资源的线程强制另一个优先级较低、而目前却拥有该资源的线程放弃该资源。如果调用SwitchToThread函数时没有其他线程能够运行,那么该函数返回FALSE,否则返回一个非0值。调用SwitchToThread与调用Sleep是相似的。差别是SwitchToThread允许优先级较低的线程运行;而即使有低优先级线程迫切需要CPU时间,Sleep也能够立即对调用线程重新进行调度。

    对称多处理机系统上Windows 2000的线程调度

    每个线程在对应的内核线程控制块中都保存着两个处理器标识:
    首选处理器:线程运行时的偏好处理器
    第二处理器:线程运行的第二选择处理器
    首选处理器是基于进程控制块的索引值来随机选择的。索引值在创建每个线程时递增。线程一旦创建后,系统就不会修改线程的首选处理器设置,但是用户可以通过SetThreadIdleProcessor来修改.

    当线程进入运行状态时,Windows首先试图调度该线程到一个空闲处理机上运行。如果有多个空闲处理机,线程调度器的调度顺序为:

    –线程的首选处理机

    –线程的第二处理机

    –当前执行处理机(即正在执行调度器代码的处理机)。

    –如果这些处理机都不是空闲的,Windows将依据处理机标识从高到低扫描系统中的空闲处理机状态,选择找到的第一个空闲处理机。

    如果线程进入就绪状态时,所有处理机都处于繁忙状态,Windows将检查一个处于运行状态或备用状态的线程,判断它是否可抢先。检查的顺序如下:

    –线程的首选处理机

    –线程的第二处理机

    –如果这两个处理机都不在线程的亲合掩码中,Windows将依据活动处理机掩码选择线程可运行的编号最大的处理机。

    Windows并不检查所有处理机上的运行线程和备用线程的优先级,而仅仅检查一个被选中处理机上的运行线程和备用线程的优先级。

    如果在被选中的处理机上没有线程可被抢先,则新线程放入相应优先级的就绪队列,并等待调度执行。为特定的处理机调度线程

    在多处理机系统,Windows不能简单地从就绪队列中取第一个线程,它要在亲合掩码限制下寻找一个满足下列条件之一的线程。

    –线程的上一次运行是在该处理机上;

    –线程的首选处理机是该处理机;

    –处于就绪状态的时间超过2个时间配额;

    –优先级大于等于24;

    如果Windows不能找到满足要求的线程,它将从就绪队列的队首取第一个线程进入运行状态。最高优先级就绪线程可能不处于运行状态.有可能出现这种情况,一个比当前正在运行线程优先级更高的线程处于就绪状态,但不能立即抢先当前线程,进入运行状态。

    空闲线程

    如果在一个处理机上没有可运行的线程,Windows会调度相应处理机对应的空闲线程。由于在多处理机系统中可能两个处理机同时运行空闲线程,所以系统中的每个处理机都有一个对应的空闲线程。Windows给空闲线程指定的线程优先级为0,该空闲线程只在没有其他线程要运行时才运行。   

      评论这张
     
    阅读(2893)| 评论(2)
    推荐 转载

    历史上的今天

    评论

    <#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
     
     
     
     
     
     
     
     
     
     
     
     
     
     

    页脚

    网易公司版权所有 ©1997-2017