Saturday, July 16, 2011

Use of Windows Thread Priorities

Use of Windows Thread Priorities


General

While looking up Windows CE I came across a statement that it had 255 priority levels.  This caused me to think again on how few levels could be set for Windows by the SetThreadPriority function of Win32Ada and WinBase.h so I Googled the subject and found Microsoft material that I’d overlooked when just using Win32Ada.

For instance, the following, which is directly taken from Microsoft material:

Scheduling Priorities

Threads are scheduled to run based on their scheduling priority. Each thread is assigned a scheduling priority. The priority levels range from zero (lowest priority) to 31 (highest priority). Only the zero-page thread can have a priority of zero. (The zero-page thread is a system thread responsible for zeroing any free pages when there are no other threads that need to run.)

The system treats all threads with the same priority as equal. The system assigns time slices in a round-robin fashion to all threads with the highest priority. If none of these threads are ready to run, the system assigns time slices in a round-robin fashion to all threads with the next highest priority. If a higher-priority thread becomes available to run, the system ceases to execute the lower-priority thread (without allowing it to finish using its time slice), and assigns a full time slice to the higher-priority thread. For more information, see Context Switches.

The priority of each thread is determined by the following criteria:
  • The priority class of its process
  • The priority level of the thread within the priority class of its process
The priority class and priority level are combined to form the base priority of a thread. For information on the dynamic priority of a thread, see Priority Boosts.

Priority Class

Each process belongs to one of the following priority classes:
IDLE_PRIORITY_CLASS
BELOW_NORMAL_PRIORITY_CLASS
NORMAL_PRIORITY_CLASS
ABOVE_NORMAL_PRIORITY_CLASS
HIGH_PRIORITY_CLASS
REALTIME_PRIORITY_CLASS

By default, the priority class of a process is NORMAL_PRIORITY_CLASS. Use the CreateProcess function to specify the priority class of a child process when you create it. If the calling process is IDLE_PRIORITY_CLASS or BELOW_NORMAL_PRIORITY_CLASS, the new process will inherit this class. Use the GetPriorityClass
function to determine the current priority class of a process and the SetPriorityClass function to change the priority class of a process.

Processes that monitor the system, such as screen savers or applications that periodically update a display, should use IDLE_PRIORITY_CLASS. This prevents the threads of this process, which do not have high priority, from interfering with higher priority threads.

Use HIGH_PRIORITY_CLASS with care. If a thread runs at the highest priority level for extended periods, other threads in the system will not get processor time. If several threads are set at high priority at the same time, the threads lose their effectiveness. The high-priority class should be reserved for threads that must respond to time-critical events. If your application performs one task that requires the high-priority class while the rest of its tasks are normal priority, use SetPriorityClass to raise the priority class of the application temporarily; then reduce it after the time-critical task has been completed. Another strategy is to create a high-priority process that has all of its threads blocked most of the time, awakening threads only when critical tasks are needed. The important point is that a high-priority thread should execute for a brief time, and only when it has time-critical work to perform.

You should almost never use REALTIME_PRIORITY_CLASS, because this interrupts system threads that manage mouse input, keyboard input, and background disk flushing. This class can be appropriate for applications that "talk" directly to hardware or that perform brief tasks that should have limited interruptions.

Priority Level

The following are priority levels within each priority class:
THREAD_PRIORITY_IDLE
THREAD_PRIORITY_LOWEST
THREAD_PRIORITY_BELOW_NORMAL
THREAD_PRIORITY_NORMAL
THREAD_PRIORITY_ABOVE_NORMAL
THREAD_PRIORITY_HIGHEST
THREAD_PRIORITY_TIME_CRITICAL

All threads are created using THREAD_PRIORITY_NORMAL. This means that the thread priority is the same as the process priority class. After you create a thread, use the SetThreadPriority function to adjust its priority relative to other threads in the process.

A typical strategy is to use THREAD_PRIORITY_ABOVE_NORMAL or THREAD_PRIORITY_HIGHEST for the process's input thread, to ensure that the application is responsive to the user. Background threads, particularly those that are processor intensive, can be set to THREAD_PRIORITY_BELOW_NORMAL or THREAD_PRIORITY_LOWEST, to ensure that they can be preempted when necessary. However, if you have a thread waiting for another thread with a lower priority to complete some task, be sure to block the execution of the waiting high-priority thread. To do this, use a wait function, critical section, or the Sleep function, SleepEx, or SwitchToThread function. This is preferable to having the thread execute a loop. Otherwise, the process may become deadlocked, because the thread with lower priority is never scheduled.

To determine the current priority level of a thread, use the GetThreadPriority function.

Base Priority

The process priority class and thread priority level are combined to form the base priority of each thread.
The following table shows the base priority for combinations of process priority class and thread priority value.

Process priority class
Thread priority level
Base priority
IDLE_PRIORITY_CLASS
THREAD_PRIORITY_IDLE
1
THREAD_PRIORITY_LOWEST
2
THREAD_PRIORITY_BELOW_NORMAL
3
THREAD_PRIORITY_NORMAL
4
THREAD_PRIORITY_ABOVE_NORMAL
5
THREAD_PRIORITY_HIGHEST
6
THREAD_PRIORITY_TIME_CRITICAL
15
BELOW_NORMAL_PRIORITY_CLASS
THREAD_PRIORITY_IDLE
1
THREAD_PRIORITY_LOWEST
4
THREAD_PRIORITY_BELOW_NORMAL
5
THREAD_PRIORITY_NORMAL
6
THREAD_PRIORITY_ABOVE_NORMAL
7
THREAD_PRIORITY_HIGHEST
8
THREAD_PRIORITY_TIME_CRITICAL
15
NORMAL_PRIORITY_CLASS
THREAD_PRIORITY_IDLE
1
THREAD_PRIORITY_LOWEST
6
THREAD_PRIORITY_BELOW_NORMAL
7
THREAD_PRIORITY_NORMAL
8
THREAD_PRIORITY_ABOVE_NORMAL
9
THREAD_PRIORITY_HIGHEST
10
THREAD_PRIORITY_TIME_CRITICAL
15
ABOVE_NORMAL_PRIORITY_CLASS
THREAD_PRIORITY_IDLE
1
THREAD_PRIORITY_LOWEST
8
THREAD_PRIORITY_BELOW_NORMAL
9
THREAD_PRIORITY_NORMAL
10
THREAD_PRIORITY_ABOVE_NORMAL
11
THREAD_PRIORITY_HIGHEST
12
THREAD_PRIORITY_TIME_CRITICAL
15
HIGH_PRIORITY_CLASS
THREAD_PRIORITY_IDLE
1
THREAD_PRIORITY_LOWEST
11
THREAD_PRIORITY_BELOW_NORMAL
12
THREAD_PRIORITY_NORMAL
13
THREAD_PRIORITY_ABOVE_NORMAL
14
THREAD_PRIORITY_HIGHEST
15
THREAD_PRIORITY_TIME_CRITICAL
15
REALTIME_PRIORITY_CLASS
THREAD_PRIORITY_IDLE
16
THREAD_PRIORITY_LOWEST
22
THREAD_PRIORITY_BELOW_NORMAL
23
THREAD_PRIORITY_NORMAL
24
THREAD_PRIORITY_ABOVE_NORMAL
25
THREAD_PRIORITY_HIGHEST
26
THREAD_PRIORITY_TIME_CRITICAL
31



I fail to understand some of the above Microsoft material.  That is, the statement “The priority levels range from zero (lowest priority) to 31 (highest priority)” make it seem like there are 32 scheduling priorities with priority level 0 only possible in a particular situation.  On the other hand, except for the REALTIME_PRIORITY_CLASS, the tables seem to show a good deal of overlap in the “base priority” from one priority class to another.  Thus it would seem that the first five priority classes only provide 15 different thread priorities from 1 through 15 with the highest priority class providing 7 more of 16, 22, 23, 24, 25, 26, and 31 for a total of 22 different priorities (excluding the special zero-page thread case of level 0).

Hence, I assumed that there are really only 15 different scheduling priority levels available for use by the Exploratory Project framework – since it doesn’t have any device drivers to use the Realtime Priority Class – thus just about double the 7 that I had when I hadn’t known about classes and, hence, was using only the default normal class.

Of these available scheduling priorities I have assigned four to the use of the framework (15, 14, 13 and 1) with the remaining 11 to be assigned to user components.  Since I have not, as yet, changed the framework categories, scheduling priorities 14 and 1 aren’t used.

The user components are assigned their priority based upon the expected duration of their execution as supplied when they register themselves.  This value could be modified based upon monitoring as they run but this has yet to be done.  Also, the exploratory project user components are merely artificial to illustrate execution possibilities and provide message traffic so they are mostly cloned had assigned the same duration so the components assigned the same won’t change much.  Therefore many components continue to be assigned the same scheduling priority and execute round robin.

Code Examples


The modified implementation occurs in two framework methods.  The first is to assign a scheduling priority based upon the provided estimated execution interval that the component provides when it registers with the framework.  The units provided in the code example are meaningless since they assume an embedded application rather than a Windows application the method takes this into account.  That is, the conversion would be changed for a different platform as well as the number of priority levels.

The second method sets the thread priority based upon the scheduling priority.

  function Process_Priority
  ( Component : in mC.Itf_Types.Component_Category_Type;
    Interval  : in Timing_Interval_Type
  ) return Process_Priority_Type is
  -- ++
  --| Logic_Flow:
  --|   This function assigns the process priority to be used with the
  --|   component time Interval for the particular operating system.
  --|
  --|   See table in Set_Thread_Priority.
  -- --

    Base_Priority
    --| Priority obtained from Time Interval
    : Process_Priority_Type;

    use type mC.Itf_Types.Component_Category_Type;

  begin -- Process_Priority

    --| Logic_Step:
    --|   Set the process priority while reserving the highest PC priority for
    --|   use by the Message Controller framework.
    --| Notes:
    --|   Only eleven priority levels available for user component use using
    --|   Win32Ada (that is, Windows).  Therefore, the following computation
    --|   has been modified to end up with priorities from 2 to 12 with 13
    --|   and 1 still available for future Framework use.

    if Component = mC.Itf_Types.Time_Critical then
      Base_Priority := 15; -- Executive Component Time Critical Priority
    elsif Component = mC.Itf_Types.Framework then
      Base_Priority := 13; -- Executive Component normal Priority
    elsif Interval <= 1 then   --  50msec
      Base_Priority := 12; -- Top User Component Priority
    elsif Interval <= 2 then   -- 100msec
      Base_Priority := 11; -- Top User Component Priority less one
    elsif Interval <= 4 then   -- 200msec
      Base_Priority := 10;
    elsif Interval <= 8 then   -- 400msec
      Base_Priority := 9;
    elsif Interval <= 16 then  -- 800msec
      Base_Priority := 8;
    elsif Interval <= 24 then  -- 1.2sec
      Base_Priority := 7;
    elsif Interval <= 50 then  -- 2.5sec
      Base_Priority := 6;
    elsif Interval <= 100 then -- 5 sec
      Base_Priority := 5;
    elsif Interval <= 200 then -- 10 sec (10,000msec)
      Base_Priority := 4;
    elsif Interval <= 400 then -- 20 sec
      Base_Priority := 3;
    else                       -- > 20 sec
      Base_Priority := 2; -- Lowest User Component Priority
    end if;

    return Base_Priority;

  end Process_Priority;

procedure Set_Thread_Priority
( Priority        : in Process_Priority_Type;
  Thread_Class    : in out Win32.DWORD;
  Thread_Priority : in out Win32.INT;
  Thread_Handle   : in Win32.Winnt.HANDLE;
  Success         : out Boolean
) is
-- ++
--| Logic_Flow:
--|   Set new scheduling priority using a combination of priority class and
--|   thread priority.
--|
--|     Priority Class                Thread Priority            Scheduling
--|                                                               Priority
--|  ---------------------------  ---------------------------  -------------------
--|  IDLE_PRIORITY_CLASS          THREAD_PRIORITY_IDLE             1     mC
--|  BELOW_NORMAL_PRIORITY_CLASS  THREAD_PRIORITY_IDLE             1     mC
--|  IDLE_PRIORITY_CLASS          THREAD_PRIORITY_LOWEST           2    user
--|  IDLE_PRIORITY_CLASS          THREAD_PRIORITY_BELOW_NORMAL     3    user
--|  BELOW_NORMAL_PRIORITY_CLASS  THREAD_PRIORITY_LOWEST           4    user
--|  BELOW_NORMAL_PRIORITY_CLASS  THREAD_PRIORITY_BELOW_NORMAL     5    user
--|  BELOW_NORMAL_PRIORITY_CLASS  THREAD_PRIORITY_NORMAL           6    user
--|  NORMAL_PRIORITY_CLASS        THREAD_PRIORITY_LOWEST           6    user
--|  BELOW_NORMAL_PRIORITY_CLASS  THREAD_PRIORITY_ABOVE_NORMAL     7    user
--|  NORMAL_PRIORITY_CLASS        THREAD_PRIORITY_BELOW_NORMAL     7    user
--|  BELOW_NORMAL_PRIORITY_CLASS  THREAD_PRIORITY_HIGHEST          8    user
--|  NORMAL_PRIORITY_CLASS        THREAD_PRIORITY_NORMAL           8    user
--|  ABOVE_NORMAL_PRIORITY_CLASS  THREAD_PRIORITY_LOWEST           8    user
--|  NORMAL_PRIORITY_CLASS        THREAD_PRIORITY_ABOVE_NORMAL     9    user
--|  ABOVE_NORMAL_PRIORITY_CLASS  THREAD_PRIORITY_BELOW_NORMAL     9    user
--|  NORMAL_PRIORITY_CLASS        THREAD_PRIORITY_HIGHEST         10    user
--|  ABOVE_NORMAL_PRIORITY_CLASS  THREAD_PRIORITY_ABOVE_NORMAL    11    user
--|  ABOVE_NORMAL_PRIORITY_CLASS  THREAD_PRIORITY_HIGHEST         12    user
--|  HIGH_PRIORITY_CLASS          THREAD_PRIORITY_NORMAL          13     mC
--|  HIGH_PRIORITY_CLASS          THREAD_PRIORITY_ABOVE_NORMAL    14     mC
--|  HIGH_PRIORITY_CLASS          THREAD_PRIORITY_HIGHEST         15     mC
--|  HIGH_PRIORITY_CLASS          THREAD_PRIORITY_TIME_CRITICAL   15 dup
--|  REALTIME_PRIORITY_CLASS      unused by Framework
--|
--|   See Process_Priority for assignment of Windows Scheduling Priorities.
-- --

  Class_Set
  --| True if thread's class was set successfully
  : Win32.BOOL;

  CurrentProcess
  --| Handle of currently running process
  : Win32.WinNT.HANDLE;

  Priority_Set
  --| True if thread's priority was set successfully
  : Win32.BOOL;

  type Windows_Scheduling_Priority_Item_Type
  is record
    Class    : Win32.DWORD;
    Priority : Win32.INT;
  end record;

  type Windows_Scheduling_Priority_Table_Type
  is array( 1..Process_Priority_Type'last )
  of Windows_Scheduling_Priority_Item_Type;

  Windows_Scheduling_Priority_Table
  --| Table of Windows Priority Classes and relative Priorities within class
  --| to obtain Scheduling Priority values
  : constant Windows_Scheduling_Priority_Table_Type
  := ( 1 => ( Class    => Win32.Winbase.IDLE_PRIORITY_CLASS,
              Priority => Win32.Winbase.THREAD_PRIORITY_IDLE ),
       2 => ( Class    => Win32.Winbase.IDLE_PRIORITY_CLASS,
              Priority => Win32.Winbase.THREAD_PRIORITY_LOWEST ),
       3 => ( Class    => Win32.Winbase.IDLE_PRIORITY_CLASS,
              Priority => Win32.Winbase.THREAD_PRIORITY_BELOW_NORMAL ),
       4 => ( Class    => Win32.Winbase.BELOW_NORMAL_PRIORITY_CLASS,
              Priority => Win32.Winbase.THREAD_PRIORITY_LOWEST ),
       5 => ( Class    => Win32.Winbase.BELOW_NORMAL_PRIORITY_CLASS,
              Priority => Win32.Winbase.THREAD_PRIORITY_BELOW_NORMAL ),
       6 => ( Class    => Win32.Winbase.NORMAL_PRIORITY_CLASS,
              Priority => Win32.Winbase.THREAD_PRIORITY_LOWEST ),
       7 => ( Class    => Win32.Winbase.NORMAL_PRIORITY_CLASS,
              Priority => Win32.Winbase.THREAD_PRIORITY_BELOW_NORMAL ),
       8 => ( Class    => Win32.Winbase.NORMAL_PRIORITY_CLASS,
              Priority => Win32.Winbase.THREAD_PRIORITY_NORMAL ),
       9 => ( Class    => Win32.Winbase.NORMAL_PRIORITY_CLASS,
              Priority => Win32.Winbase.THREAD_PRIORITY_ABOVE_NORMAL ),
      10 => ( Class    => Win32.Winbase.NORMAL_PRIORITY_CLASS,
              Priority => Win32.Winbase.THREAD_PRIORITY_HIGHEST ),
      11 => ( Class    => Win32.Winbase.ABOVE_NORMAL_PRIORITY_CLASS,
              Priority => Win32.Winbase.THREAD_PRIORITY_ABOVE_NORMAL ),
      12 => ( Class    => Win32.Winbase.ABOVE_NORMAL_PRIORITY_CLASS,
              Priority => Win32.Winbase.THREAD_PRIORITY_HIGHEST ),
      13 => ( Class    => Win32.Winbase.HIGH_PRIORITY_CLASS,
              Priority => Win32.Winbase.THREAD_PRIORITY_NORMAL ),
      14 => ( Class    => Win32.Winbase.HIGH_PRIORITY_CLASS,
              Priority => Win32.Winbase.THREAD_PRIORITY_ABOVE_NORMAL ),
      15 => ( Class    => Win32.Winbase.HIGH_PRIORITY_CLASS,
              Priority => Win32.Winbase.THREAD_PRIORITY_TIME_CRITICAL )
     );

  use type Win32.BOOL;

begin -- Set_Thread_Priority

  --| Logic_Step:
  --|   Obtain handle of current process/application.

  CurrentProcess := Win32.Winbase.GetCurrentProcess;

  --| Logic_Step:
  --|   Set Windows priority class and relative priority with class.

  Thread_Class    := Windows_Scheduling_Priority_Table(Priority).Class;
  Thread_Priority := Windows_Scheduling_Priority_Table(Priority).Priority;

  --| Logic_Step:
  --|   Set new Windows priority class and relative priority with class for
  --|   the particular application thread.

  if Priority /= 0 then

    Process_Lock;

    Class_Set := Win32.Winbase.SetPriorityClass
                 ( hProcess        => CurrentProcess,
                   dwPriorityClass => Thread_Class );
    Priority_Set := Win32.Winbase.SetThreadPriority
                    ( hThread   => Thread_Handle,
                      nPriority => Thread_Priority  );

    Process_Unlock;

    if Priority_Set = Win32.True then
      Success := True;
    else
      Success := False;
    end if;

  else -- leave priority as is

    Thread_Class := Win32.Winbase.GetPriorityClass
                    ( hProcess => CurrentProcess );
    Thread_Priority := Win32.Winbase.GetThreadPriority
                       ( hThread => Thread_Handle );
    Success := True;

  end if;

end Set_Thread_Priority;