有没有过这样的经验?你坐在你的车子里,目的地还在好几公里之遥,而时间已经很晚了。你拼命想告诉那些挡住你去路的人们,今天这个约会对你是多么多么重要,能不能请他们统统……呃……滚到马路外?很不幸,道路系统并没有纳入所谓的优先权观念。如果有某条专用道是给“非常重要”的通行所用的,你就可以摆脱那些如潮水般在你四周的车辆和行人,岂不甚妙?
    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. UEditor编辑器第一次赋值失败的解决方法

    网上查了很多方式都不是很好用,最后想到了这样的处理方式 首先在js中定义一个全局变量 var ue = null; 然后在初始化显示编辑器的时候js这样写 if (ue == null) { ue = ...

  2. MySQL(三)之SQL语句分类、基本操作、三大范式

    一.SQL语句的分类     DML(Data Manipulation Langauge,数据操纵/管理语言) (insert,delete,update,select)     DDL(Data ...

  3. ThinkPhp5源码剖析之Cache

    为什么需要Cache(缓存)? 假设现在有一个小说网,有非常多的读者,有一篇新的章节更新了,那么可能一分钟内有几万几十万的访问量. 如果没有缓存,同样的内容就要去数据库重复查询,那可能网站一下就挂掉了 ...

  4. 车牌识别OCR—易泊时代智慧城市解决方案模块

    牌识别(License Plate Recognition,LPR) 是视频图像识别技术在智能交通领域中的一个模块.车牌识别运用OCR技术,将视频流或图片中的汽车牌照从复杂的应用场景中提取并识别出来, ...

  5. ios 初体验<UIButton 控件>

    1.创建UIButton 跟其他方式不同,不是直接alloc,init 创建 用工厂化方式创建 UIButton *sureBtn = [UIButton buttonWithType:UIButto ...

  6. Java 优先队列

    Java PriorityQueue 优先队列是一种重要的数据结构,其利用的是小/大顶堆来实现的. Java中提供了PriorityQueue,PriorityQueue是基于小顶堆实现的无界优先队列 ...

  7. 一,ESP8266下载和刷固件

    用自己的小板测试...... 安排上呢 一, ESP8266下载和刷固件(Lua开发----体验一下lua开发的魅力所在) 二, 控制一个灯亮灭 三, TCP服务器 四, TCP客户端 五, UDP ...

  8. 构建具有用户身份认证的 React + Flux 应用程序

    原文:Build a React + Flux App with User Authentication 译者:nzbin 译者的话:这是一篇内容详实的 React + Flux 教程,文章主要介绍了 ...

  9. PHP初入--添加内容到框框里并删除

    <!DOCTYPE html><html> <head> <meta charset="UTF-8"> <title>& ...

  10. 带你简单了解python协程和异步

    带你简单了解python的协程和异步 前言 对于学习异步的出发点,是写爬虫.从简单爬虫到学会了使用多线程爬虫之后,在翻看别人的博客文章时偶尔会看到异步这一说法.而对于异步的了解实在困扰了我好久好久,看 ...