经出现在线程的队列中,因此该线程就是可调度的线程。此外,键盘设备驱动程序也能够告诉系统暂时提高线程的优先级等级。该线程的优先级等级可能提高2级,其当前优先级等级改为1 5。 系统在优先级为1 5时为一个时间片对该线程进行调度。一旦该时间片结束,系统便将线程的优先级递减1,使下一个时间片的线程优先级降为1 4。该线程的第三个时间片按优先级等级1 3来执行。如果线程要求执行更多的时间片,均按它的基本优先级等级1 3来执行。
注意,线程的当前优先级等级决不会低于线程的基本优先级等级。此外,导致线程成为可调度线程的设备驱动程序可以决定优先级等级提高的数量。M i c r o s o f t并没有规定各个设备驱动程序可以给线程的优先级提高多少个等级。这样就使得M i c r o s o f t可以不断地调整线程优先级提高的动态等级,以确定最佳的总体响应性能。
系统只能为基本优先级等级在1至1 5之间的线程提高其优先级等级。实际上这是因为这个范围称为动态优先级范围。此外,系统决不会将线程的优先级等级提高到实时范围(高于1 5)。由于实时范围中的线程能够执行大多数操作系统的函数,因此给等级的提高规定一个范围,就可以防止应用程序干扰操作系统的运行。另外,系统决不会动态提高实时范围内的线程优先级等级。 有些编程人员抱怨说,系统动态提高线程优先级等级的功能对他们的线程性能会产生一种不良的影响,为此M i c r o s o f t增加了下面两个函数,这样就能够使系统的动态提高线程优先级等级的功能不起作用:
BOOL SetProcessPriorityBoost(HANDLE hProcess, BOOL DisablePriorityBoost);
BOOL SetThreadPriorityBoost(HANDLE hThread, BOOL DisablePriorityBoost);
S e t P r o c e s s P r i o r i t y B o o s t负责告诉系统激活或停用进行中的所有线程的优先级提高功能,而S e t T h r e a d P r i o r i t y B o o s t则让你激活或停用各个线程的优先级提高功能。这两个函数具有许多相似的共性,可以用来确定是激活还是停用优先级提高功能:
BOOL GetProcessPriorityBoost(HANDLE hProcess, PBOOL pDisablePriorityBoost);
BOOL GetThreadPriorityBoost(HANDLE hThread, PBOOL pDisablePriorityBoost);
对于这两个函数中的每个函数,可以传递想要查询的进程或线程的句柄,以及由函数设置的B O O L的地址。
Windows 98没有提供这4个函数的有用的实现代码。它们全部返回FA L S E,后来对G e t L a s t E r r o r的调用将返回E R R O R _ C A L L _ N O T _ I M P L E M E N T E D。
另一种情况也会导致系统动态地提高线程的优先级等级。比如有一个优先级为4的线程准备运行但是却不能运行,因为一个优先级为8的线程正连续被调度。在这种情况下,优先级为4的线程就非常渴望得到C P U时间。当系统发现一个线程在大约3至4 s内一直渴望得到C P U时间,它就将这个渴望得到C P U时间的线程的优先级动态提高到1 5,并让该线程运行两倍于它的时间量。当到了两倍时间量的时候,该线程的优先级立即返回到它的基本优先级。
7.9.2 为前台进程调整调度程序
当用户对进程的窗口进行操作时,该进程就称为前台进程,所有其他进程则称为后台进程。当然,用户希望他正在使用的进程比后台进程具有更强的响应性。为了提高前台进程的响应性,Wi n d
o w s能够为前台进程中的线程调整其调度算法。对于Windows 2000来说,系统可以为前台进程的线程提供比通常多的C P U时间量。这种调整只能在前台进程属于正常优先级类的进程时才能进行。如果它属于其他任何优先级类,就无法进行任何调整。
Windows 2000实际上允许用户对这种调整进行相应的配置。在System Properties(系统属性)对话框的A d v a n c e d选项卡上,用户可以单击Performance Options(性能选项)按钮,打开图7 - 3所示的对话框。
图7-3 Performance Options 对话框
如果用户选择优化应用程序的性能,系统就执行配置的调整。如果用户选择优化后台服务程序的性能,系统就不进行调整。当安装Windows 2000的专业版时,A p p l i c a t i o n s就会被默认选定。对于Windows 2000的所有其他版本,则默认选定Background Services,因为计算机将主要由非交互式用户使用。
当进程移到前台时, Windows 98也会对正常优先级类的进程中的线程调度算法进行调整。当一个优先级为正常的进程移到前台时,系统便将最低、低于正常、正常、高于正常和最高等优先级的线程的优先级提高1,优先级为空闲和关键时间的线程的优先级则不予提高。因此,在正常优先级类的进程中运行的、其相对优先级为正常的线程,它的优先级等级是9而不是8。当进程返回后台时,进程中的线程便自动返回它们定义好的基本优先级等级。
Windows 98 Windows 98没有提供允许用户配置这种调整手段的任何用户界面,因为Windows 98不是作为专用服务器来运行的。
将进程改为前台进程的原因是,使它们能够对用户的输入更快地作出响应。如果不改为前台进程,那么在后台的正常打印进程与在后台接收用户输入的正常进程就会平等地争用C P U时间。用户会发现文本无法在前台应用程序中顺利地显示。但是,由于系统改变了前台进程的线程优先级,前台进程的线程就能对用户的输入更好地作出响应。
7.9.3 Scheduling Lab示例应用程序
使用Scheduling Lab应用程序“ 0 7 S c h e d L a b . e x e”(见后面的清单7 - 1),可以对进程优先级类和相对线程优先级进行操作试验,以了解它们对系统的总体性能产生的影响。该应用程序
的源代码和源文件位于本书所附光盘上的0 7 - S c h e d L a b目录中。当启动该程序时,就会出现图7 - 4所示的窗口。
开始时,主线程总是处于繁忙状态,因此C P U的使用量立即跳到1 0 0 %。该主线程连续递增一个数字,并将它添加给右边的列表框。这个数字并没有任何意义,它只是显示线程正在忙于进行什么操作。若要了解线程调度对系统会产生什么实际影响,建议至少要同时运行该示例应用程序的两个实例,看一看改变一个实例的优先级会对另一个实例带来的影响。也可以运行Ta s kM a n a g e r,以便监控所有实例的C P U使用量。
当进行这些测试时,C P U的使用量开始时将上升为1 0 0 %,该应用程序的所有实例将获得大约相等的C P U时间(Task Manager应该显示应用程序的所有实例大致相同的C P U使用量百分比)。
图7-4 进程优先级类和相对线程优先级的试验窗口
如果将一个实例的优先级类改为高于正常或高优先级类,那么应该看到它得到了大部分的C P U使用量。而其他实例中的数字滚动则没有规律。但是其他实例的数字不会完全停止滚动,因为系统将为渴求C P U时间的线程自动执行优先级的动态提高。不管怎样,可以随意调整优先级类和相对线程优先级,以了解它们对其他实例的影响。我有目的地对Scheduling Lab应用程序进行了编码,这样就无法将进程改为实时优先级类,这可以防止操作系统线程的不正常的运行。如果想要试用实时优先级,必须自己修改源代码。
可以使用S l e e p域,使主线程在0到9 9 9 9之间的任意毫秒内无法调度。请试用这项功能,并观察传递仅为1 m s的睡眠值时可以重新获得多少C P U时间。在我的300MHz Pentium II笔记本电脑上,我赢得了9 9 %的C P U时间。
单击S u s p e n d(暂停)按钮,可使主线程产生一个子线程。这个子线程能够暂停主线程的运行,并显示图7 - 5所示的消息框。
图7-5 消息框
当这个消息框显示时,主线程将完全暂停运行,并且不使用任何C P U时间。子线程也不使用任
何C P U时间,因为它是在等待用户执行某种操作。当消息框显示时,可以将它移到应用程序的主窗口,然后将它移开,这样就能够看到主窗口。由于主线程已经暂停运行,因此主窗口将无法接收任何窗口消息(包括W M _ PA I N T)。这证明该线程已经暂停运行。当关闭该消息框时,主线程就恢复运行,C P U使用量回到1 0 0 %。
若要再进行一次试验,请打开前一节介绍的Performance Options对话框,将A p p l i c a t i o n改为Background Services,或者将Background Services改为A p p l i c a t i o n。然后打开S c h e d L a b程序的多个实例,将它们全部设置为正常优先级类,并激活其中的一个,使之成为一个前台进程。这时就能够看到性能的设置对前台/后台进程产生的影响。
清单7-1 SchedLab示例应用程序
/******************************************************************************
Module: SchedLab.cpp
Notices: Copyright (c) 2000 Jeffrey Richter
******************************************************************************/
#include \#include
#include
///////////////////////////////////////////////////////////////////////////////
DWORD WINAPI ThreadFunc(PVOID pvParam) {
HANDLE hThreadPrimary = (HANDLE) pvParam; SuspendThread(hThreadPrimary); chMB(
\
\ \thread.\\n\
ResumeThread(hThreadPrimary); CloseHandle(hThreadPrimary);
// To avoid deadlock, call EnableWindow after ResumeThread. EnableWindow(
GetDlgItem(FindWindow(NULL, TEXT(\
IDC_SUSPEND), TRUE); return(0); }
///////////////////////////////////////////////////////////////////////////////
BOOL Dlg_OnInitDialog (HWND hwnd, HWND hwndFocus, LPARAM lParam) {
chSETDLGICONS(hwnd, IDI_SCHEDLAB);
// Initialize process priority classes
HWND hwndCtl = GetDlgItem(hwnd, IDC_PROCESSPRIORITYCLASS);
int n = ComboBox_AddString(hwndCtl, TEXT(\ ComboBox_SetItemData(hwndCtl, n, HIGH_PRIORITY_CLASS);
// Save our current priority class
DWORD dwpc = GetPriorityClass(GetCurrentProcess());
if (SetPriorityClass(GetCurrentProcess(), BELOW_NORMAL_PRIORITY_CLASS)) {
// This system supports the BELOW_NORMAL_PRIORITY_CLASS class
// Restore our original priority class
SetPriorityClass(GetCurrentProcess(), dwpc);
// Add the Above Normal priority class
n = ComboBox_AddString(hwndCtl, TEXT(\
ComboBox_SetItemData(hwndCtl, n, ABOVE_NORMAL_PRIORITY_CLASS);
dwpc = 0; // Remember that this system supports below normal }
int nNormal = n = ComboBox_AddString(hwndCtl, TEXT(\ ComboBox_SetItemData(hwndCtl, n, NORMAL_PRIORITY_CLASS);
if (dwpc == 0) {
// This system supports the BELOW_NORMAL_PRIORITY_CLASS class