Each individual thread is scheduled CPU time by the Kernel. The Kernel handles all the management and scheduling of threads. When a Windows process is created, its primary thread is automatically created by the system. The primary thread is able to create additional threads. In Windows, if any of a processes’s threads is still running, the process is considered to be alive. A process only terminates when all of the threads in the process have ended.
Scheduling decisions are made on a thread basis. The OS schedules threads in a round-robin fashion based on the threads’ scheduling priority. Threads are given quantums of time to run on a given CPU. When the hardware timer determines that a thread has run long enough, an interrupt occurs. When that happens, or when the thread voluntarily gives up control of the CPU, a context switch is performed and another thread is executed. There is a small performance penalty for each context switch. If the scheduler is switching between threads in the same process, in what is known as an intro=process context switch, the transition is relatively straightforward. An interprocess context switch requires the OS to reconfigure the CPU’s address translation hardware with the address space of the process of the thread.
A running thread is one that is executing on a CPU. There is only one running thread per CPU at any given moment. When a thread reaches the point where it cannot proceed until a resource is available or an operation completes, the thread becomes blocked. Such a thread is placed by the scheduler into a pool with the other blocked threads. A ready thread is one that is not running, but is up for being run. Ready threads are slated for execution depending on their order in the queue and their priority. A blocked thread is any thread that is waiting for a kernel object to become signaled or for an I/O operation to complete. A suspended thread is a thread that has been suspended by Windows for a short amount of time.
Handles belong to the process, not to individual threads, so they can be shared by threads belonging to the same process. However, the handles are meaningless to threads outside of the process in which the handles were received.
Thread priority is used to augment a round-robin scheduling algorithm. A numeric value is used to indicate teh relative importance of a thread’s work; the scheduler places the ready thread into one of the ready queues based on that number. The thread’s priority is actually an offset of the process’s priority values.
Creating a thread starts with the CreateThread function. The CreateThread function takes six parameters.
The lpThreadAttributes parameter describes the security attributes that should be applied to the new thread. NULL here will specify using the default security attributes. The cbStack variable is the byte size of the new thread’s stack. We use 0 here to select the default value. Every thread has its own stack. The lpStartAddr parameter points to the function to be executed.
The fdwCreate parameter can take one of two values. If the value is 0, the thread starts immediately. If the value is CREATE_SUSPENDED, the system creates the thread and prepares to execute the thread function, but does not start executing. The last parameter, lpThreadId, must be a valid address to a DWORD that will store the new thread’s ID value.
The CreateThread function returns a handle to a thread, or else returns FALSE if there was a problem creating a thread.
Note that we use the CloseHandle function to release kernel objects when we have finished using them. It’s important to close thread handles in order to prevent resource leaks. The CloseHandle function takes one argument, an object of type HANDLE and returns a Boolean value indicating the success or failure of the function.