美国《独立宣言》中说:人生而平等。操作系统中说:平等你妹!进程和线程必须得有优先级!
我说:这段是看书后随笔写的日志,恳请牛逼的明白人指正拍砖!
首先需要注意的第一点是:在Windows中,只有线程是可调度的,进程只是线程的容器,进程为线程提供内存,提供可以共享的数据,进程不可调度。
Windows是一个抢占式多任务的操作系统,那么既然叫做抢占式,最关键的就是在于怎么抢。
按照传统来说,如果只有一个CPU,那么为了显得操作系统可以同时做很多事情,通常的做法就是为每一个调度单元分配一个时间片,当某一个单元的时间片耗尽之后操作系统选择另一个可调度单元进行运行。
这种特性在抢占式多任务的OS中基本得到支持,不同的地方在于:优先级高的线程将会直接抢走优先级低线程的时间片。
例如目前一个优先级为4的线程正在运行,某一优先级为5的线程所等待的事件发生,那么,该线程不会等到等级4的线程完成它的时间片,Windows会直接挂起等级4的线程而将5的线程调入CPU进行运行。从某种角度来说,等级5的线程抢走了等级4的CPU时间。
当某一时刻,线程A和B都处于可运行状态时,谁的优先级高,谁就被调入CPU。
但是高优先级就运行的时间多也不是绝对的,例如高优先级的A正在等待某一资源,但是这个资源被B持有,B在释放在该资源前不断的完成其它不需要等待的操作,那么造成的结果就是A一直都被挂起,直到B释放了资源。这种情况叫做优先级反转。低优先级的线程不断运行,而高的得不到运行。解决优先级反转的方法就是在高低不同的线程之间尽量或者不使用共享的同步对象。
再者,有些时候高优先级的线程也必须主动放弃自己的时间片来使得其它线程运行。依然例如B占用着某一个资源正在操作,A这时候就应该果断放弃自己的时间片让B完成操作好释放共享资源。
要放弃自己的时间片,可以使用两种方法
Sleep(0)
SwitchToThread()
第一种方法将使得CPU查看当前与该线程同优先级或者更高优先级的线程是否可调度,第二者则是全系统的查看哪一个线程快被“饿死”了,不仅仅是查看同级或者高级的线程。
但是以上的区别只在XP和2000上有效,从2003Server开始,这两个都调度所有线程
当你需要更改某一个线程的优先级时,你会奇怪的发现,Windows有两个设置优先级的函数,一个是进程优先级,一个是线程优先级。既然操作系统已经将进程作为容器,那么为什么还有进程优先级这种概念呢?
说一句实话,其中原因也只有盖茨知道。
必须清楚的是,微软没有公开它的调度算法,Windows会将进程优先级和线程优先级进行综合的计算,最后将其映射到一个数字上,这个数字代表线程实际的优先级。至于这个数字怎么映射,为什么要那么映射,SDK文档上没有陈述。
在设置进程优先级API中,有一个参数是“实时”等级,记得除非你真的知道你在干什么,否则永远不要把进程的优先级设置在这个等级上。因为:
在实时等级中,即使是优先级设置最低的线程,其映射后的优先级都会高于在非实时等级下最高优先级的线程
这意味着,进程A,线程a,进程B,线程b,如果A是实时的,而B不是,则优先级(a) > 优先级(b) 始终成立。
因为很多操作系统的线程都在比实时要低的优先级上运行,因此实时等级进程中的线程会干扰到操作系统的运行,显然,提升进程到这个等级需要调整Token的特权,只有使用管理员模式运行的Token才能获得这种特权。
至于其它进程优先等级下,线程的优先级怎么映射,只有以下一个结论。
若优先级(a) = 优先级(b),则若优先级(A) > 优先级(B),有实际优先级(a) >= 实际优先级(b)
除非特别指明,否则进程或者线程的优先级均为普通(Normal)。进程的优先级可以在创建进程时通过CreateFlag指定。
注意:CreateThread/_beginthreadex并没有像CreateProcess那样提供上述的机制,如果你想创建一个线程并同时指定它的优先级,你得这么做
1.用挂起标志创建线程 |
2.设定线程优先级 |
3.允许线程继续运行 |
一旦设定了优先级,那么线程的优先级是否就一直不变呢?Jeffrey Richter andChristophe Nasarre说,操作系统还可以动态的调节线程的优先级。
线程优先级的映射值被称为基础优先级,当某个事件,一般都是I/O事件发生的时候,操作系统都会动态提升一下线程的优先级。比方说,某一个等级13的线程会被提升到15,在下一个时间片后它会降低到14,再下一个时间片后回到13。线程至少在基础优先级上运行。
注意:线程的优先级永远都不会被提升到实时等级的任何一个映射值,同时,位于实时优先级下进程的所有线程也永远都不会受到动态提升。
在书中,实时等级的最低映射值为16,整个系统的最高映射值为31,则如果记线程优先级为T的话,那么,如果该线程所处的进程不是Real Time(实时)等级的进程则
1<=T<=15 永远成立,否则16<=T<=31永远成立。
如果某一个线程等待CPU可用的时间太长,Windows会动态提升它的等级到15,2个时间片后,它回到自身的基础优先级。
通过在SetThreadPriority里设定THREAD_MODE_BACKGROUND_BEGIN,线程告诉操作系统自己希望进行低优先级的I/O,通过THREAD_MODE_BACKGROUND_END来结束后台处理模式。需要注意的是:
一个线程可以更改自己的优先级,但是任何一个线程都不能更改其它进程和线程的I/O优先级。
线程还可以设定自己对某一个特定文件(很多资源都会表现为文件)的I/O优先级(通过SetFileInformationByHandle)。
注意:SetFileInformationByHandle完全无视进程或者线程是否进入了背景操作模式(THREAD_MODE_BACKGROUND_BEGIN),它会严格按照指定的优先级来处理该文件的I/O。