Programming Priorities

[Previous] [Next]

So how is a process assigned a priority class? Well, when you call CreateProcess, you can pass the desired priority class in the fdwCreate parameter. The table below shows the priority class identifiers.

Priority Class Symbolic Identifiers
Real-time REALTIME_PRIORITY_CLASS
High HIGH_PRIORITY_CLASS
Above normal ABOVE_NORMAL_PRIORITY_CLASS
Normal NORMAL_PRIORITY_CLASS
Below normal BELOW_NORMAL_PRIORITY_CLASS
Idle IDLE_PRIORITY_CLASS

It might seem odd that the process that creates a child process chooses the priority class at which the child process runs. Let's consider Explorer as an example. When you use Explorer to run an application, the new process runs at normal priority. Explorer has no idea what the process does or how often its threads need to be scheduled. However, once the child process is running, it can change its own priority class by calling SetPriorityClass:

 BOOL SetPriorityClass( HANDLE hProcess, DWORD fdwPriority); 

This function changes the priority class identified by hProcess to the value specified in the fdwPriority parameter. The fdwPriority parameter can be one of the identifiers shown in the table above. Because this function takes a process handle, you can alter the priority class of any process running in the system as long as you have a handle to it and sufficient access.

Normally, a process will attempt to alter its own priority class. Here is an example of how to have a process set its own priority class to idle:

 BOOL SetPriorityClass( GetCurrentProcess(), IDLE_PRIORITY_CLASS); 

Here is the complementary function used to retrieve the priority class of a process:

 DWORD GetPriorityClass(HANDLE hProcess); 

As you might expect, this function returns one of the identifiers listed in the table above.

When you invoke a program using the command shell, the program's starting priority is normal. However, if you invoke the program using the Start command, you can use a switch to specify the starting priority of the application. For example, the following command entered at the command shell causes the system to invoke the Calculator and initially run it at idle priority:

 C:\>START /LOW CALC.EXE 

The Start command also recognizes the /BELOWNORMAL, /NORMAL, /ABOVENORMAL, /HIGH, and /REALTIME switches to start executing an application at their respective priority classes. Of course, once an application starts executing, it can call SetPriorityClass to alter its own priority to whatever it chooses.

Windows 98
The Windows 98 Start command does not support any of these switches. Processes started from the Windows 98 command shell always run using the normal priority class.

The Windows 2000 Task Manager allows the user to change the priority class of a process. The figure below shows the Task Manager's Processes tab, which shows all the processes currently running. The Base Pri column shows each process's priority class. You can alter a process's priority class by selecting a process and then selecting an option from the context menu's Set Priority submenu.

When a thread is first created, its relative thread priority is always set to normal. It has always seemed odd to me that CreateThread doesn't offer a way for the caller to set the new thread's relative priority. To set and get a thread's relative priority, you must call these functions:

 BOOL SetThreadPriority( HANDLE hThread, int nPriority); 

Of course, the hThread parameter identifies the single thread whose priority you want to change, and the nPriority parameter is one of the seven identifiers listed in the following table.

Relative Thread Priority Symbolic Constant
Time-critical THREAD_PRIORITY_TIME_CRITICAL
Highest THREAD_PRIORITY_HIGHEST
Above normal THREAD_PRIORITY_ABOVE_NORMAL
Normal THREAD_PRIORITY_NORMAL
Below normal THREAD_PRIORITY_BELOW_NORMAL
Lowest THREAD_PRIORITY_LOWEST
Idle THREAD_PRIORITY_IDLE

Here is the complementary function for retrieving a thread's relative priority:

 int GetThreadPriority(HANDLE hThread); 

This function returns one of the identifiers listed in the table above.

To create a thread with an idle relative thread priority, you execute code similar to the following:

 DWORD dwThreadID; HANDLE hThread = CreateThread(NULL, 0, ThreadFunc, NULL, CREATE_SUSPENDED, &dwThreadID); SetThreadPriority(hThread, THREAD_PRIORITY_IDLE); ResumeThread(hThread); CloseHandle(hThread); 

Note that CreateThread always creates a new thread with a normal relative thread priority. To have the thread execute using idle priority, you pass the CREATE_SUSPENDED flag to CreateThread; this prevents the thread from executing any code at all. Then you call SetThreadPriority to change the thread to an idle relative thread priority. You then call ResumeThread so that the thread can be schedulable. You don't know when the thread will get CPU time, but the scheduler takes into account the fact that this thread has an idle thread priority. Finally, you close the handle to the new thread so that the kernel object can be destroyed as soon as the thread terminates.

NOTE
Windows does not offer a function that returns a thread's priority level. This omission is deliberate. Remember that Microsoft reserves the right to change the scheduling algorithm at any time. You should not design an application that requires specific knowledge of the scheduling algorithm. If you stick with process priority classes and relative thread priorities, your application should run well today and on future versions of the system.

Dynamically Boosting Thread Priority Levels

The system determines the thread's priority level by combining a thread's relative priority with the priority class of the thread's process. This is sometimes referred to as the thread's base priority level. Occasionally, the system boosts the priority level of a thread—usually in response to some I/O event such as a window message or a disk read.

For example, a thread with a normal thread priority in a high priority class process has a base priority level of 13. If the user presses a key, the system places a WM_KEYDOWN message in the thread's queue. Because a message has appeared in the thread's queue, the thread is schedulable. In addition, the keyboard device driver can tell the system to temporarily boost the thread's level. So the thread might be boosted by 2 and have a current priority level of 15.

The thread is scheduled for one time slice at priority 15. Once that time slice expires, the system drops the thread's priority by 1 to 14 for the next time slice. The thread's third time slice is executed with a priority level of 13. Any additional time slices required by the thread are executed at priority level 13, the thread's base priority level.

Note that a thread's current priority level never goes below the thread's base priority level. Also note that the device driver that causes the thread to be schedulable determines the amount of the boost. Again, Microsoft does not document how much boost a thread will get by any individual device driver. This allows Microsoft to continuously fine-tune the dynamic boosts to determine the best overall responsiveness.

The system only boosts threads that have a base priority level between 1 and 15. In fact, this is why this range is referred to as the dynamic priority range. In addition, the system never boosts a thread into the real-time range (above 15). Since threads in the real-time range perform most operating system functions, enforcing a cap on the boost prevents an application from interfering with the operating system. Also, the system never dynamically boosts threads in the real-time range (16 through 31).

Some developers complained that the system's dynamic boosts had an adverse affect on their threads' performance, so Microsoft added the following two functions to let you disable the system's dynamic boosting of thread priority levels:

 BOOL SetProcessPriorityBoost( HANDLE hProcess, BOOL DisablePriorityBoost); BOOL SetThreadPriorityBoost( HANDLE hThread, BOOL DisablePriorityBoost); 

SetProcessPriorityBoost tells the system to enable or disable priority boosting for all threads within a process; SetThreadPriorityBoost lets you enable or disable priority boosting for individual threads. These two functions have counterparts that allow you to determine whether priority boosting is enabled or disabled:

 BOOL GetProcessPriorityBoost( HANDLE hProcess, PBOOL pDisablePriorityBoost); BOOL GetThreadPriorityBoost( HANDLE hThread, PBOOL pDisablePriorityBoost); 

To each of these functions, you pass the handle of the process or thread that you want to query along with the address of a BOOL that will be set by the function.

Windows 98
Windows 98 offers no useful implementation of these four functions. They all return FALSE, and a subsequent call to GetLastError returns ERROR_CALL_NOT_IMPLEMENTED.

Another situation causes the system to dynamically boost a thread's priority level. Imagine a priority 4 thread that is ready to run but cannot because a priority 8 thread is constantly schedulable. In this scenario, the priority 4 thread is being starved of CPU time. When the system detects that a thread has been starved of CPU time for about three to four seconds, it dynamically boosts the starving thread's priority to 15 and allows that thread to run for twice its time quantum. When the double time quantum expires, the thread's priority immediately returns to its base priority.

Tweaking the Scheduler for the Foreground Process

When the user works with windows of a process, that process is said to be the foreground process and all other processes are background processes. Certainly, a user would prefer the process that he or she is using to behave more responsively than the background processes. To improve the responsiveness of the foreground process, Windows tweaks the scheduling algorithm for threads in the foreground process. For Windows 2000, the system gives foreground process threads a larger time quantum than they would usually receive. This tweak is performed only if the foreground process is of the normal priority class. If it is of any other priority class, no tweaking is performed.

Windows 2000 actually allows a user to configure this tweaking. On the Advanced tab of the System Properties dialog box, the user can click the Performance Options button, which causes the following dialog box to appear.

If the user chooses to optimize performance for applications, the system performs the tweaking. If the user chooses to optimize performance for background services, no tweaking is performed. When you install Windows 2000 Professional Edition, Applications is selected by default. For all other editions of Windows 2000, Background Services is the default because it is expected that the machine will be used primarily by noninteractive users.

Windows 98 also tweaks the threads in a normal priority class process when it moves to the foreground. When a normal process is brought to the foreground, the system increases the priority of the lowest, below normal, normal, above normal, and highest threads by 1 when the process is moved to the foreground; the idle and time-critical threads do not have their priorities boosted. So a thread with normal relative thread priority running in a normal priority class process has a priority level of 9 instead of 8. When the process returns to the background, the threads within the process automatically return to their defined base priority level.

Windows 98
Windows 98 does not offer any user interface that allows a user to configure this tweaking because Windows 98 is not designed to run as a dedicated server machine.

The reason for this change to foreground processes is to make them react faster to the user's input. Without this change, a normal process printing in the background and a normal process accepting user input in the foreground would compete equally for the CPU's time. The user, of course, would see that text is not appearing smoothly in the foreground application. But because the system alters the foreground process's threads, the foreground process's threads can process the user's input more responsively.

The Scheduling Lab Sample Application

Using the Scheduling Lab application, "07 SchedLab.exe" (listed in Figure 7-1), you can experiment with process priority classes and relative thread priorities to see their effect on the system's overall performance. The source code and resource files for the application are in the 07-SchedLab directory on the companion CD-ROM. When you start the program, the window shown here appears.

Initially, the primary thread is always busy so your CPU usage immediately jumps to 100 percent. The primary thread constantly increments a number and adds it to the list box on the right. The number doesn't have any meaning—it simply shows that the thread is busy doing something. To get a feel for how thread scheduling actually affects the system, I recommend that you run at least two instances of this sample application simultaneously to see how changing the priorities of one instance affects the other instances. You can also run Task Manager and monitor the CPU usage of all instances.

When you perform these tests, the CPU usage will initially go to 100 percent and all instances of the application will get about equal CPU time. (Task Manager should show about the same percentage of CPU usage for all instances.) If you change one instance's priority class to above normal or high, you should see it get the bulk of the CPU usage. The scrolling of numbers in the other instances will become erratic. However, the other instances do not stop scrolling completely because of the dynamic boosting that the system automatically performs for starving threads. Anyway, you can play with the priority class and relative thread priorities to see how they affect the other instances. I purposely coded the Scheduling Lab application so it doesn't allow you to change the process to the real-time priority class because this prevents operating system threads from performing properly. If you want to experiment with real-time priority, you must modify the source code yourself.

You can use the Sleep field to stop the primary thread from being schedulable for any number of milliseconds from 0 to 9999. Experiment with this and see how much CPU processing time you recover by passing a sleep value of just 1 millisecond. On my 300 MHz Pentium II Notebook computer, I gain back 99 percent—quite a drop!

Clicking on the Suspend button causes the primary thread to spawn a secondary thread. This secondary thread suspends the primary thread and displays the following message box.

click to view at full size.

While this message box is displayed, the primary thread is completely suspended and uses no CPU time. The secondary thread also does not use any CPU time because it is simply waiting for the user to do something. While the message box is displayed, you can move it over the application's main window and then move it away so you can see the main window. Because the primary thread is suspended, the main window will not receive any window messages (including WM_PAINT). This is proof positive that the thread is suspended. When you dismiss the message box, the primary thread is resumed and the CPU usage goes back up to 100 percent.

For one more test, display the Performance Options dialog box discussed in the previous section, and change the setting from Application to Background Services or vice versa. Then take multiple instances of the SchedLab program, set them all to the normal priority class, and activate one of them to make it the foreground process. You'll see what effect the performance setting has on the foreground/background processes.

Figure 7-1. The SchedLab sample application

SchedLab.cpp

 /****************************************************************************** Module: SchedLab.cpp Notices: Copyright (c) 2000 Jeffrey Richter ******************************************************************************/ #include "..\CmnHdr.H" /* See Appendix A. */ #include <windowsx.h> #include <tchar.h> #include <process.h> // For _beginthreadex #include "Resource.H" /////////////////////////////////////////////////////////////////////////////// DWORD WINAPI ThreadFunc(PVOID pvParam) { HANDLE hThreadPrimary = (HANDLE) pvParam; SuspendThread(hThreadPrimary); chMB( "The Primary thread is suspended.\n" "It no longer responds to input and produces no output.\n" "Press OK to resume the primary thread & exit this secondary thread.\n"); ResumeThread(hThreadPrimary); CloseHandle(hThreadPrimary); // To avoid deadlock, call EnableWindow after ResumeThread. EnableWindow( GetDlgItem(FindWindow(NULL, TEXT("Scheduling Lab")), 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("High")); 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("Above normal")); ComboBox_SetItemData(hwndCtl, n, ABOVE_NORMAL_PRIORITY_CLASS); dwpc = 0; // Remember that this system supports below normal } int nNormal = n = ComboBox_AddString(hwndCtl, TEXT("Normal")); ComboBox_SetItemData(hwndCtl, n, NORMAL_PRIORITY_CLASS); if (dwpc == 0) { // This system supports the BELOW_NORMAL_PRIORITY_CLASS class // Add the Below Normal priority class n = ComboBox_AddString(hwndCtl, TEXT("Below normal")); ComboBox_SetItemData(hwndCtl, n, BELOW_NORMAL_PRIORITY_CLASS); } n = ComboBox_AddString(hwndCtl, TEXT("Idle")); ComboBox_SetItemData(hwndCtl, n, IDLE_PRIORITY_CLASS); ComboBox_SetCurSel(hwndCtl, nNormal); // Initialize thread relative priorities hwndCtl = GetDlgItem(hwnd, IDC_THREADRELATIVEPRIORITY); n = ComboBox_AddString(hwndCtl, TEXT("Time critical")); ComboBox_SetItemData(hwndCtl, n, THREAD_PRIORITY_TIME_CRITICAL); n = ComboBox_AddString(hwndCtl, TEXT("Highest")); ComboBox_SetItemData(hwndCtl, n, THREAD_PRIORITY_HIGHEST); n = ComboBox_AddString(hwndCtl, TEXT("Above normal")); ComboBox_SetItemData(hwndCtl, n, THREAD_PRIORITY_ABOVE_NORMAL); nNormal = n = ComboBox_AddString(hwndCtl, TEXT("Normal")); ComboBox_SetItemData(hwndCtl, n, THREAD_PRIORITY_NORMAL); n = ComboBox_AddString(hwndCtl, TEXT("Below normal")); ComboBox_SetItemData(hwndCtl, n, THREAD_PRIORITY_BELOW_NORMAL); n = ComboBox_AddString(hwndCtl, TEXT("Lowest")); ComboBox_SetItemData(hwndCtl, n, THREAD_PRIORITY_LOWEST); n = ComboBox_AddString(hwndCtl, TEXT("Idle")); ComboBox_SetItemData(hwndCtl, n, THREAD_PRIORITY_IDLE); ComboBox_SetCurSel(hwndCtl, nNormal); Edit_LimitText(GetDlgItem(hwnd, IDC_SLEEPTIME), 4); // Maximum of 9999 return(TRUE); } /////////////////////////////////////////////////////////////////////////////// void Dlg_OnCommand (HWND hwnd, int id, HWND hwndCtl, UINT codeNotify) { switch (id) { case IDCANCEL: PostQuitMessage(0); break; case IDC_PROCESSPRIORITYCLASS: if (codeNotify == CBN_SELCHANGE) { SetPriorityClass(GetCurrentProcess(), (DWORD) ComboBox_GetItemData(hwndCtl, ComboBox_GetCurSel(hwndCtl))); } break; case IDC_THREADRELATIVEPRIORITY: if (codeNotify == CBN_SELCHANGE) { SetThreadPriority(GetCurrentThread(), (DWORD) ComboBox_GetItemData(hwndCtl, ComboBox_GetCurSel(hwndCtl))); } break; case IDC_SUSPEND: // To avoid deadlock, call EnableWindow before creating // the thread which calls SuspendThread. EnableWindow(hwndCtl, FALSE); HANDLE hThreadPrimary; DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(), &hThreadPrimary, THREAD_SUSPEND_RESUME, FALSE, DUPLICATE_SAME_ACCESS); DWORD dwThreadID; CloseHandle(chBEGINTHREADEX(NULL, 0, ThreadFunc, hThreadPrimary, 0, &dwThreadID)); break; } } /////////////////////////////////////////////////////////////////////////////// INT_PTR WINAPI Dlg_Proc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { chHANDLE_DLGMSG(hwnd, WM_INITDIALOG, Dlg_OnInitDialog); chHANDLE_DLGMSG(hwnd, WM_COMMAND, Dlg_OnCommand); } return(FALSE); } /////////////////////////////////////////////////////////////////////////////// int WINAPI _tWinMain (HINSTANCE hinstExe, HINSTANCE, LPTSTR pszCmdLine, int) { HWND hwnd = CreateDialog(hinstExe, MAKEINTRESOURCE(IDD_SCHEDLAB), NULL, Dlg_Proc); BOOL fQuit = FALSE; while (!fQuit) { MSG msg; if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { // IsDialogMessage allows keyboard navigation to work properly. if (!IsDialogMessage(hwnd, &msg)) { if (msg.message == WM_QUIT) { fQuit = TRUE; // For WM_QUIT, terminate the loop. } else { // Not a WM_QUIT message. Translate it and dispatch it. TranslateMessage(&msg); DispatchMessage(&msg); } } // if (!IsDialogMessage()) } else { // Add a number to the listbox static int s_n = -1; TCHAR sz[20]; wsprintf(sz, TEXT("%u"), ++s_n); HWND hwndWork = GetDlgItem(hwnd, IDC_WORK); ListBox_SetCurSel(hwndWork, ListBox_AddString(hwndWork, sz)); // Remove some strings if there are too many entries while (ListBox_GetCount(hwndWork) > 100) ListBox_DeleteString(hwndWork, 0); // How long should the thread sleep int nSleep = GetDlgItemInt(hwnd, IDC_SLEEPTIME, NULL, FALSE); if (chINRANGE(1, nSleep, 9999)) Sleep(nSleep); } } DestroyWindow(hwnd); return(0); } //////////////////////////////// End of File ////////////////////////////////// 

SchedLab.rc

 //Microsoft Developer Studio generated resource script. // #include "resource.h" #define APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 2 resource. // #include "afxres.h" ///////////////////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // English (U.S.) resources #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) #ifdef _WIN32 LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US #pragma code_page(1252) #endif //_WIN32 ///////////////////////////////////////////////////////////////////////////// // // Dialog // IDD_SCHEDLAB DIALOGEX 0, 0, 209, 70 STYLE DS_3DLOOK | DS_CENTER | WS_MINIMIZEBOX | WS_VISIBLE | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_NOPARENTNOTIFY | WS_EX_CLIENTEDGE CAPTION "Scheduling Lab" FONT 8, "MS Sans Serif" BEGIN LTEXT "&Process priority class:",IDC_STATIC,4,6,68,8 COMBOBOX IDC_PROCESSPRIORITYCLASS,84,4,72,80,CBS_DROPDOWNLIST | WS_TABSTOP LTEXT "&Thread relative priority:",IDC_STATIC,4,20,72,8 COMBOBOX IDC_THREADRELATIVEPRIORITY,84,18,72,76,CBS_DROPDOWNLIST | WS_TABSTOP LTEXT "Sleep (0 to 9999 &ms):",IDC_STATIC,4,36,68,8 EDITTEXT IDC_SLEEPTIME,84,34,32,14,ES_NUMBER PUSHBUTTON "&Suspend",IDC_SUSPEND,4,52,49,14 LISTBOX IDC_WORK,160,4,48,60,NOT LBS_NOTIFY | LBS_NOINTEGRALHEIGHT | LBS_NOSEL | WS_TABSTOP END ///////////////////////////////////////////////////////////////////////////// // // DESIGNINFO // #ifdef APSTUDIO_INVOKED GUIDELINES DESIGNINFO DISCARDABLE BEGIN IDD_SCHEDLAB, DIALOG BEGIN LEFTMARGIN, 7 RIGHTMARGIN, 202 TOPMARGIN, 7 BOTTOMMARGIN, 63 END END #endif // APSTUDIO_INVOKED #ifdef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // TEXTINCLUDE // 1 TEXTINCLUDE DISCARDABLE BEGIN "resource.h\0" END 2 TEXTINCLUDE DISCARDABLE BEGIN "#include ""afxres.h""\r\n" "\0" END 3 TEXTINCLUDE DISCARDABLE BEGIN "\r\n" "\0" END #endif // APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Icon // // Icon with lowest ID value placed first to ensure application icon // remains consistent on all systems. IDI_SCHEDLAB ICON DISCARDABLE "SchedLab.ico" #endif // English (U.S.) resources ///////////////////////////////////////////////////////////////////////////// #ifndef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 3 resource. // ///////////////////////////////////////////////////////////////////////////// #endif // not APSTUDIO_INVOKED 



Programming Applications for Microsoft Windows
Programming Applications for Microsoft Windows (Microsoft Programming Series)
ISBN: 1572319968
EAN: 2147483647
Year: 1999
Pages: 193
Authors: Jeffrey Richter
BUY ON AMAZON

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net