有没有过这样的经验?你坐在你的车子里,目的地还在好几公里之遥,而时间已经很晚了。你拼命想告诉那些挡住你去路的人们,今天这个约会对你是多么多么重要,能不能请他们统统……呃……滚到马路外?很不幸,道路系统并没有纳入所谓的优先权观念。如果有某条专用道是给“非常重要”的通行所用的,你就可以摆脱那些如潮水般在你四周的车辆和行人,岂不甚妙?
    Win32 有所谓的优先权(priority)观念,用以决定下一个获得 CPU 时间的线程是谁。较高优先权的线程必然获得较多的 CPU 时间。关于优先权的完整讨论其实相当复杂。你可以无分轩轾地给予每一个线程相同的优先权,这可能会使你承担不少麻烦。你也可以明智地使用优先权,使自己能够调整程序的执行次序。例如你可以设定你的 GUI 线程有较高优先权,使它对于用户的反应能够比较平顺一些,或者你可以改变 worker 线程的优先权,使它们只在系统的闲置时间(idle time)里工作。
    Win32 优先权是以数值表现的,并以进程的“优先权类别(priority class)”、线程的“优先权层级 (priority level)”和操作系统当时采用的“动态提升(Dynamic Boost)”作为计算基准。所有因素放在一起,最后获得一个 0~31 的数值。拥有最高优先权之线程,即为下一个将执行起来的线程。如果你有一大把 worker 线程,其“优先权类别”和“优先权层级”都相同,那么就每一个轮流执行。这是所谓的 “round robin” 调度方式。如果你有一个线程总是拥有最高优先权,那么它就永远获得 CPU 时间,别人都别玩了。这就是为什么必须明智而谨慎地使用优先权的原因。

优先权类别(Priority Class)
    “优先权类别”是进程的属性之一。这个属性可以表现出这一进程和其他进程比较之下的重要性。Win32 提供四种优先权类别,每一个类别对应一个基本的优先权层级。表格5-1 展示了四个优先权类别。
表格5-1 优先权类别(Priority Classes)
优先权类别(Priority Classes) 基础优先权值(base priority)
HIGH_PRIORITY_CLASS     13
IDLE_PRIORITY_CLASS     4
NORMAL_PRIORITY_CLASS     7 or 8(译注:有些资料上写 7 or 9)
REALTIME_PRIORITY_CLASS 24
    大部分程序使用 NORMAL_PRIORITY_CLASS。少数情况下才会考虑使用其他类别。例如,Task Manager 就是使用 HIGH_PRIORITY_CLASS,所以即使其他程序处于非常忙碌的状态下,它也总是能够有所反应。
    Windows NT 中有一个好例子,可以说明到底应不应该使用某些特定的优先权类别。以 OpenGL 完成的屏幕保护程序(screen saver)看似密集地使用了所有 CPU 时间。如果这个屏幕保护程序启动时你正在进行系统备份,备份操作会慢下来, 像蜗牛一样。但如果屏幕保护程序使用IDLE_PRIORITY_CLASS,它就只会在 CPU 绝对空闲的时候才执行。
    最后一个类别是 REALTIME_PRIORITY_CLASS。这个类别用以协助解决一些和时间有密切关系的工作。举个例子,如果有个程序必须反应一个设备驱动程序的行为,而该驱动程序用来实时监控(real-time monitoring)真实世界中的一台仪器,那么将该进程设为这个优先权类别,就可以使它甚至优于核心进程和设备驱动程序。这个优先权类别不应该用于标准 GUI 程序或甚至于典型的服务器程序。
    优先权类别适用于进程而非线程。你可以利用 SetPriorityClass() 和GetPriorityClass() 来调整和验证其值。本书并未涵盖这两个函数的说明。
优先权层级(Priority Level)
    线程的优先权层级(Priority Level)是对进程的优先权类别的一个修改,使你能够调整同一个进程内的各线程的相对重要性。一共有七种优先权层级,显示于表格5-2 中。
表格5-2 优先权层级(Priority Levels)
优先权层级(Priority Levels)     调整值
THREAD_PRIORITY_HIGHEST     +2
THREAD_PRIORITY_ABOVE_NORMAL     +1
THREAD_PRIORITY_NORMAL         0
THREAD_PRIORITY_BELOW_NORMAL     –1
THREAD_PRIORITY_LOWEST         –2
THREAD_PRIORITY_IDLE         Set to 1
THREAD_PRIORITY_TIME_CRITICAL     Set to 15
注意:对于 REALTIME_PRIORITY_CLASS 的调整值,有点不同于上表所列。

    优先权层级可以利用 SetThreadPriority() 改变之。
    BOOL SetThreadPriority(
        HANDLE hThread,
        int nPriority
    );
    参数
    hThread     代表欲调整优先权的那个线程。
    nPriority     表格5-2 所显示的数值。
返回值
    如果函数成功,就传回表格5-2 所列的其中一个值。如果函数失败,就传回 FALSE。GetLastError() 可以获得更详细的信息。
    线程目前的优先权层级可以利用 GetThreadPriority() 获知。
int GetThreadPriority(
    HANDLE hThread
);
参数
    hThread     代表一个线程
返回值
    如果函数成功, 就传回 TRUE 。如果函数失败, 就传回THREAD_PRIORITY_ERROR_RETURN。GetLastError() 可以获得更详细的信息。

KERNEL32.DLL 中的优先权
    我使用 Windows 95 所提供的 PVIEW32,观察我的系统中的各个进程,结果如图5-1 所示。我看到系统模块 KERNEL32.DLL 有八个线程,其优先权类别是 HIGH_PRIORITY_CLASS,所以其基础优先权值为 13。检查其线程,发现有四个线程的优先权层级是 THREAD_PRIORITY_LOWEST,所以其优先权为11。三个线程的优先权层级是 THREAD_PRIORITY_NORMAL,所以其优先权为 13。一个线程的优先权层级是 THREAD_PRIORITY_TIME_CRITICAL,所以其优先权为 15。最后这个线程应该总是能够在任何其他“非实时线程”之前被调度程序选中执行。
图5-1 Windows 95 中的PVIEW32(图因win版本过旧省略)

动态提升(Dynamic Boost)
    决定线程真正优先权的最后一个因素是其目前的动态提升值(Dynamic Boost)。所谓动态提升是对优先权的一种调整,使系统能够机动对待线程,以强化程序的可用性。
    最容易被我们观察的,便是 Windows NT 施行于所有前台程序的“线程动态提升”。图5-2 的 系统属性 中的【性能】附页,允许用户指定前台程序应该对用户有怎样的回应。你可以在【我的电脑】中按下右键,并选择【属性】而获得这一画面。
    默认情况下图5-2 的“动态提升”被设定为最大,这使得拥有键盘焦点的程序(前台程序)的优先权得以提升 +2。这个设定使得前台程序比后台程序获得较多的 CPU 时间,因此即使系统忙碌,前台程序还是容易保持其 UI 敏感度。
    图5-2 Windows NT 4.0 的系统属性(图因win版本过旧省略)
    第二种优先权动态提升也适用于同属一个进程的线程,用以反应用户的输入或磁盘的输入。例如,只要线程获得键盘输入,该线程就得到一个 +5 的优先权调整值。这使得该线程有机会处理那个输入,并且提供立即的回应给用户。其他可能引起优先权动态提升的情况还包括鼠标消息、计时器消息等等。
    最后一种优先权动态提升的情况可能发生在任何一个线程(不限属于哪一个进程)身上。那是在一个“等待状态”获得满足时发生的,例如有一个线程正在等待一个 mutex,当 Wait...() 返回时,该线程的优先权会获得动态提升。这样的提升意味着 critical sections 将尽可能地被快速处理,而等待时间将尽可能地缩短。

更令人战栗的 Busy Waiting
    你已经在第2章看到了,一个 busy loop 是如何地吃掉 CPU 时间。一旦你开始调整线程优先权,情况有可能变得更糟。书附盘片中有一个程序名为BUSYPRIO , 以 THREAD_PRIORITY_HIGHEST 来运行主线程, 以THREAD_PRIORITY_NORMAL 来运行 worker 线程。
    如果你执行这个程序,你可能会看到一些非所期望的结果:程序永远结束不了。为什么?主线程不断等待,所以不断需要 CPU 时间。而由于它的优先权比 worker 线程高,所以 worker 线程永远没有机会获得 CPU 时间。这种情况称为 starvation(饥饿)。
    BUSYPRIO 显示,小心翼翼地设定线程优先权是件多么重要的事情。改变线程优先权可能会打开潘朵拉的盒子,一些新的问题跑出来,死锁的阴影也潜在性地酝酿着。虽然优先权的基础知识很简单,但其实用面却可能很复杂。如果你的目标是保持简单,那就还是避免处理“优先权”这个烫手山芋吧。

第5章 不要让线程成为脱缰的野马(Keeping your Threads on Leash) ---线程优先权(Thread priority)的更多相关文章

  1. 第5章 不要让线程成为脱缰的野马(Keeping your Threads on Leash) ----初始化一个线程

    使用线程的一个常见问题就是如何能够在一个线程开始运行之前,适当地将它初始化.初始化最常见的理由就是为了调整优先权.另一个理由是为了在SMP 系统中设定线程比较喜欢的 CPU.第10 章谈到 MFC 时 ...

  2. 第5章 不要让线程成为脱缰的野马(Keeping your Threads on Leash) ---干净的终止一个线程

    干净的终止一个线程  我曾经在第2章产生一个后台线程,用以输出一张屏幕外的 bitmap 图.我们必须解决的一个最复杂的问题就是,如果用户企图结束程序,而这张bitmap 图尚未完成,怎么办?第2章的 ...

  3. 第5章 不要让线程成为脱缰的野马(Keeping your Threads on Leash) ---简介

    这一章描述如何初始化一个新线程,如何停止一个执行中的线程,以及如何了解并调整线程优先权.    读过这一章之后,你将有能力回答一个 Win32 多线程程序设计的最基本问题.你一定曾经在 Usenet ...

  4. 第11章 Windows线程池(2)_Win2008及以上的新线程池

    11.2 Win2008以上的新线程池 (1)传统线程池的优缺点: ①传统Windows线程池调用简单,使用方便(有时只需调用一个API即可) ②这种简单也带来负面问题,如接口过于简单,无法更多去控制 ...

  5. 线程操作案例--生产者与消费者,Object类对线程的支持

    本章目标 1)加深对线程同步的理解 2)了解Object类中对线程的支持方法. 实例 生产者不断生产,消费者不断消费产品. 生产者生产信息后将其放到一个区域中,之后消费者从区域中取出数据. 既然生产的 ...

  6. Linux线程的实现 & LinuxThread vs. NPTL & 用户级内核级线程 & 线程与信号处理

    另,线程的资源占用可见:http://www.cnblogs.com/charlesblc/p/6242111.html 进程 & 线程的很多知识可以看这里:http://www.cnblog ...

  7. java笔记--用ThreadLocal管理线程,Callable<V>接口实现有返回值的线程

    用ThreadLocal管理线程,Callable<V>接口实现有返回值的线程 ThreadLocal在我的笔记"关于线程同步"的第5种方式里面有介绍,这里就不多说了. ...

  8. 线程间操作无效: 从不是创建控件“”的线程访问它~~~的解决方法~ 线程间操作无效: 从不是创建控件“Control Name'”的线程访问它问题的解决方案及原理分析

    看两个例子,一个是在一个进程里设置另外一个进程中控件的属性.另外一个是在一个进程里获取另外一个进程中控件的属性. 第一个例子 最近,在做一个使用线程控制下载文件的小程序(使用进度条控件显示下载进度)时 ...

  9. Tomcat线程池,更符合大家想象的可扩展线程池

    因由 说起线程池,大家可能受连接池的印象影响,天然的认为,它应该是一开始有core条线程,忙不过来了就扩展到max条线程,闲的时候又回落到core条线程,如果还有更高的高峰,就放进一个缓冲队列里缓冲一 ...

随机推荐

  1. django模板(过滤器)

    -------------------django内建的过滤器-------------------1.add 使用形式为:{{ value | add: "2"}} 意义:将va ...

  2. matlab-常用函数(2)

    isempty(A) 功能解释 isempty()用来判断 一个矩阵是否为空矩阵,其用法相当于C语言中的"a==NULL". 当参数为空矩阵时,该函数返回逻辑值"1&qu ...

  3. C#后台调用浏览器打开下载连接地址的三种方法

    一.从注册表中读取到本地计算机默认浏览器,然后调用下载. private void button1_Click(object sender, EventArgs e)   {       //从注册表 ...

  4. js实现换肤效果

    一,js换肤的基本原理 基本原理很简单,就是使用 JS 切换对应的 CSS 样式表文件.例如导航网站 Hao123 的右上方就有网页换肤功能.除了切换 CSS 样式表文件之外,通常的网页换肤还需要通过 ...

  5. 转:【Java集合源码剖析】LinkedHashmap源码剖析

    转载请注明出处:http://blog.csdn.net/ns_code/article/details/37867985   前言:有网友建议分析下LinkedHashMap的源码,于是花了一晚上时 ...

  6. 201521123018 《Java程序设计》第4周学习总结

    1. 本章学习总结 2. 书面作业 Q1.注释的应用:使用类的注释与方法的注释为前面编写的类与方法进行注释,并在Eclipse中查看.(截图) Q2.面向对象设计(大作业1-非常重要) 2.1 讲故事 ...

  7. 201521123092《java程序设计》第三周学习总结

    #1. 本章学习总结 你对于本章知识的学习总结 #2. 书面作业 **Q1.  代码阅读 public class Test1 { private int i = 1;//这行不能修改 private ...

  8. 201521123110第二周Java学习总结

    1.本章学习总结 本周的Java学习相对前一周更进了一步,初步学习了Java简单的输入和输出,String类的对象创建后不能修改,它是不可变的,在Java中浮点型默认是double型与C中的int型不 ...

  9. 201521123077 《Java程序设计》第11周学习总结

    1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结异常与多线程相关内容. 线程的同步(加锁防止多个线程同时访问) synchronized关键字修饰 可以使用于方法前或者方法内做同步 ...

  10. 201521123069 《Java程序设计》 第10周学习总结

    1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结异常与多线程相关内容. (1)我们可以利用堆栈追踪来获得异常发生的根源(堆栈追踪信息最顶层). (2)自定义异常:不是由Java系统 ...