To give you control over thread scheduling and stack management, Threads.h++ defines a set of attributes that are used when a thread is created to define or control the various thread scheduling and stack allocation behaviors.
The purpose of this section is to describe the Win32-specific support, behavior, and restrictions for these attributes.
NOTE: Once you begin using thread attributes, you risk compromising the cross-platform portability of your code.
Problems with portability arise largely because scheduling and stack allocation policies tend to vary significantly between each of the environments supported by Threads.h++. Rogue Wave has made every attempt to alleviate or hide these differences without prohibiting access to the platform-specific controls that some developers will require. It is up to you to carefully review and understand the implementation differences between each platform that you plan to support.
Threads.h++ provides numerous feature test macros and functions that allow you to determine, at compile or run-time, which attributes and attribute values are supported by this environment. See the Threads.h++ User's Guide for a complete description of these tests.
It is important to note that the member functions used to manipulate attributes are always present, regardless of whether the current environment supports those attributes.
This section describes the Win32-specific support, behavior, and restrictions for thread scheduling attributes.
The start policy attribute is fully supported by the Win32 implementation of Threads.h++ and defaults to RW_THR_START_RUNNING.
In Win32, all threads are scheduled by the kernel and are have a fixed contention scope value of RW_THR_SYSTEM_SCOPE.
You may query the contention scope, and may set the contention scope to RW_THR_SYSTEM_SCOPE, but any attempt to change the scope to RW_THR_PROCESS_SCOPE will produce an exception.
The scheduling inheritance policy attribute is fully supported by the Win32 implementation of Threads.h++ and defaults to RW_THR_INHERIT.
The concurrency policy attribute is not supported in the Win32 implementation of Threads.h++. Attempts to get or set this attribute value will result in exceptions.
In Win32, the scheduling policy of a thread is related to the priority class of the thread's process. Since the process is assigned to a priority class, all threads within a process will have the same scheduling policy. To change the scheduling policy used, you would have to change the priority class of the process. Changing the priority class will affect all threads within the process.
Threads.h++ does not provide any mechanism for changing the priority class of a process; you can do this directly by using the SetPriorityClass() function in the Win32 API. You should use care when doing this; running a process in the HIGH_PRIORITY_CLASS or REALTIME_PRIORITY_CLASS may interfere with normal operation of the operating system.
Threads.h++ maps the Win32 priority classes to RWSchedulingPolicy values as follows:
Win32 Priority Class | Threads.h++ Scheduling Policy |
IDLE_PRIORITY_CLASS |
RW_THR_TIME_SLICED_DYNAMIC |
NORMAL_PRIORITY_CLASS |
RW_THR_TIME_SLICED_DYNAMIC |
HIGH_PRIORITY_CLASS |
RW_THR_TIME_SLICED_DYNAMIC |
REALTIME_PRIORITY_CLASS |
RW_THR_TIME_SLICED_FIXED |
The default priority class for a process is NORMAL_PRIORITY_CLASS, unless the priority class of the creating process is IDLE_PRIORITY_CLASS, in which case the default priority class of the child process is IDLE_PRIORITY_CLASS.
Each Win32 priority class defines a different base priority value for threads created in that class; see "Scheduling Priority" below for additional information.
You may query for the default scheduling policy, and may set the scheduling policy to match the current scheduling policy, but any attempt to change the policy will produce an RWTHROperationNotAvailable exception.
The default scheduling policy is determined by querying the API for the priority-class of the current process and converting that result to a scheduling policy according to the mapping defined in the previous table.
As with scheduling policy, the true priority of a thread under Win32 is related to the priority class of the thread's process. Win32 defines a range of priority values from 1 to 31 for use in scheduling threads. Each thread is assigned a base-priority within this range according to the priority class of the process and a priority offset defined by the thread.
The priority class of the process defines a base priority level for all threads in that process. Each thread possesses a priority level offset, that when combined with the base-priority of the priority-class, gives a thread its own base priority value.
The scheduler uses this base priority to determine the thread's dynamic priority, the value used to make scheduling decisions. A thread's dynamic priority is never less than its base priority.
The scheduler raises and lowers the dynamic priority of a thread to enhance its responsiveness when significant things happen to the thread, unless the thread is executing in a process with REALTIME_PRIORITY_CLASS; threads in this priority class will not have their priority dynamically altered.
Dynamic priority changes, or boosts can occur in the following situations:
A window receives input (such as timer messages, mouse move messages, keyboard input).
The parent process owns a window that has become active.
A wait for disk or keyboard I/O is satisfied.
After raising a thread's dynamic priority, the scheduler reduces that priority by one level each time the thread completes a time slice, until the thread drops back to its base priority.
In addition to these dynamic priority boosts, the scheduler raises the priority class of the process associated with the foreground window, so it is greater than or equal to the priority class of any background processes. The process's priority class returns to its original setting when it is no longer in the foreground.
Dynamic boosts are also used to break the deadlock that can result from priority inversion. The Windows NT scheduler does this by randomly boosting the priority of threads that are ready to run (in this case the low priority lock-holders). The low priority threads run long enough to let go of their lock (exit the critical section), and the high- priority thread gets the lock back. If the low-priority thread doesn't get enough CPU time to free its lock the first time, it will get another chance on the next scheduling round.
Priority inversion is handled differently in Windows 95. If a high-priority thread is dependent on a low priority thread which will not be allowed to run because a medium priority thread is getting all of the CPU time, the system recognizes that the high-priority thread is dependent on the low priority thread and will boost the low priority thread's priority up to the priority of the high-priority thread. This will allow the formerly low priority thread to run and unblock the high-priority thread that was waiting on it.
Since threads operating in the real-time priority class do not receive dynamic boosts, care must be taken to insure that these threads do not deadlock as the result of priority inversion.
This table shows the legal range of Threads.h++ priority values for Win32, and the mapping between those priority values and the Win32 priority values:
Threads.h++ Priority Values | Win32 Priority Values |
-3 |
THREAD_PRIORITY_IDLE |
-2 |
THREAD_PRIORITY_LOWEST (-2) |
-1 |
THREAD_PRIORITY_BELOW_NORMAL (-1) |
0 |
THREAD_PRIORITY_NORMAL |
+1 |
THREAD_PRIORITY_ABOVE_NORMAL (+1) |
+2 |
THREAD_PRIORITY_HIGHEST (+2) |
+3 |
THREAD_PRIORITY_TIME_CRITICAL |
The following table shows how the Threads.h++ priority values map to the true priority levels used by the operating system. This mapping is directly related to the current priority class of the process. (The base priority of each priority class appears underlined.)
Threads.h++ Priority Levels | |||||||
Win32 Process Priority Class | -3 | -2 | -1 | 0 | +1 | +2 | +3 |
IDLE_PRIORITY_CLASS |
1 |
2 |
3 |
4 |
5 |
6 |
15 |
NORMAL_PRIORITY_CLASS (Background window) |
1 |
5 |
6 |
7 |
8 |
9 |
15 |
NORMAL_PRIORITY_CLASS (Foreground window) |
1 |
7 |
8 |
9 |
10 |
11 |
15 |
HIGH_PRIORITY_CLASS |
1 |
11 |
12 |
13 |
14 |
15 |
15 |
REALTIME_PRIORITY_CLASS |
16 |
22 |
23 |
24 |
25 |
26 |
31 |
The default priority class for a process is NORMAL_PRIORITY_CLASS, unless the priority class of the creating process is IDLE_PRIORITY_CLASS, in which case the default priority class of the child process is IDLE_PRIORITY_CLASS. This means that the typical default priority for a Win32 thread is either 7 or 9, depending on whether the process is associated with a foreground or background window. Threads with a base priority level above 11 can interfere with the normal operation of the operating system.
The time-slice quantum attribute is not supported in the Win32 implementation of Threads.h++. Attempts to get or set this attribute value will result in exceptions.
This section describes the Win32-specific support, behavior, and restrictions for stack attributes.
Win32 provides somewhat limited control over the stack allocation process. Unless otherwise indicated, the default stack attributes for a newly created thread are inherited from the creating thread. Both the amount of virtual address space to reserve, and the amount of physical memory to commit are inherited. Win32 provides no method for interrogating these values.
The stack size for the initial thread in a process is defined by the default stack attributes of that process. The default stack attributes for a process are 1MB reserved and one page (4KB) committed unless specified otherwise using a linker switch or .DEF file entry.
Following allocation, the operating system will grow the stack as needed by committing one-page blocks (4K on an x86 machine) out of the reserved stack memory. Once all of the reserved memory has been committed, the system will attempt to continue to grow the stack into the memory adjacent to the memory reserved for the stack.
Once the stack grows to the point where there is no free memory adjacent to the stack (and this may happen as soon as the initial reserve has been committed), the stack cannot grow any farther. At this point, further stack growth will produce exceptions, error, or other unpredictable behavior. To avoid this situation, you should take care to specify a sufficient amount of reserved memory space for your stacks when you link your program.
Each new thread gets its own stack space of committed and reserved memory. The Win32 API allows a new commit size to be specified when creating a thread. If a new size is not specified, the new thread takes on the same stack size and commit size as the thread that created it, whether that be the default values, the values defined in the DEF file, or values defined with a linker switch. If the commit size specified is larger than the default process stack size, the new thread is given a stack allocation equal to the commit size.
Win32 automatically commits more memory from the reserved stack space as needed, but cannot commit more than the total amount initially reserved. Remember that the only resource consumed by reserving space is addresses in the virtual address space of your process; no memory or pagefile space is allocated until the memory is committed. Therefore, there is little harm in reserving a large area if it might be needed.
To summarize, the reserve size and default commit size of the stack is established at link-time. You may choose to commit a different amount when you create a thread, but you can only increase the reserve size of the stack by committing more memory than is reserved. Win32 provides no method for querying the stack size at run-time, so you will not be able to determine, at run-time, what portion of the reserved space you have mapped into memory when you specify a commit size, nor will you be able to determine when it may be necessary to commit more memory in order to increase the stack size.
Since you have no control over the reserve size of the stack, the getMinStackSize() function is not supported under Win32, and will produce an RWTHROperationUnsupported exception if used.
Stack Reserve Size. Even though stack reserve size is defined in the context of the Win32 system, the amount of memory reserve is fixed at link-time and cannot be obtained from the Win32 API. Therefore, Threads.h++ does not support this attribute in its Win32 implementation. Attempts to get or set the reserve size attribute will result in exceptions.
Stack Commit Size. The commit size attribute allows you to specify the amount of physical memory or pagefile space to commit to a thread's stack when that thread is created.
Under Win32, the default value for the commit size attribute is inherited from the creating thread, and cannot be queried unless the querying thread was created with a non-default commit size. The typical default commit size is one page or 4KB of memory, unless otherwise specified by a .DEF file entry or linker switch.
Threads.h++ imposes a lower limit of 1 for the minimum commit size, and imposes no limit on the maximum stack size. The maximum stack size is effectively limited by available virtual memory space and pagefile size. If the stack commit size specified is too large for the available resources, an exception will be produced when a thread is created and initialized using the offending thread attribute instance. Any such exception is thrown by the start() function invocation that attempted to create the thread.
User-managed stacks are not supported in the Win32 implementation of Threads.h++. Attempts to get or set the user stack address or user stack size attribute values will result in exceptions.
©Copyright 2000, Rogue Wave Software, Inc.
Contact Rogue Wave about documentation or support issues.