Each process is started with a single thread, often called the primary thread, but can create additional threads from any of its threads.
A thread is the entity within a process that can be scheduled for execution. All threads of a process share its virtual address space and system resources. In addition, each thread maintains exception handlers, a scheduling priority, thread local storage, a unique thread identifier, and a set of structures the system will use to save the thread context until it is scheduled. The thread context includes the thread’s set of machine registers, the kernel stack, a thread environment block, and a user stack in the address space of the thread’s process. Threads can also have their own security context, which can be used for impersonating clients.
Windows supports preemptive multitasking, which creates the effect of simultaneous execution of multiple threads from multiple processes. On a multiprocessor computer, the system can simultaneously execute as many threads as there are processors on the computer.
CreateProcess() : Create a Process with the attributes set. GetPriorityClass() and SetPriorityClass() can be used to process Priority of the created Process. The Priority class can be one of the following:
The following are priority levels within each priority class. GetThreadPriority() and SetThreadPriority() are related APIs. The levels are THREAD_PRIORITY_IDLE / LOWEST / BELOW_NORMAL / NORMAL (default) / ABOVE_NORMAL, HIGHEST and TIME_CRITICAL.
By default child process By default, a child process inherits a copy of the environment block of the parent process.
CreateThread() : Create a Thread with the attributes set. Priority levels can be set as discussed above. By default threads are created in executable state. If we use CREATE_SUSPENDED flag while creating a thread, the thread will not be executed immediately. We need to issue ResumeThread() to bring it back to executable state. ExitThread() will terminate a thread in order. GetExitCodeThread() returns the termination status of a thread.
The SwitchToThread() function causes the calling thread to yield execution to another thread that is ready to run on the current processor. The operating system selects the thread to yield to. This can be implemented with Sleep() also. Sleep(0) causes the thread to relinquish the remainder of its time slice to any other thread of equal priority that is ready to run. If there are no other threads of equal priority ready to run, the function returns immediately, and the thread continues execution.
TerminateThread() can result in the following problems (ExitThread() is preferred):
If the target thread owns a critical section, the critical section will not be released.
If the target thread is allocating memory from the heap, the heap lock will not be released.
If the target thread is executing certain kernel32 calls when it is terminated, the kernel32 state for the thread’s process could be inconsistent.
If the target thread is manipulating the global state of a shared DLL, the state of the DLL could be destroyed, affecting other users of the DLL.
Thread Local Storage (TLS): Thread local storage (TLS) enables multiple threads of the same process to use an index allocated by the TlsAlloc function to store and retrieve a value that is local to the thread. In this example, an index is allocated when the process starts. When each thread starts, it allocates a block of dynamic memory and stores a pointer to this memory in the TLS slot using the TlsSetValue function. The CommonFunc function uses the TlsGetValue function to access the data associated with the index that is local to the calling thread. Before each thread terminates, it releases its dynamic memory. Before the process terminates, it calls TlsFree to release the index. This is quite interesting concept worth to describe more here. Here’s how the APIs work:
First define a structure that contains all the data that needs to be unique among the threads. For example,
int a ;
int b ;
DATA, * PDATA ;
The primary thread calls TlsAlloc to obtain an index value:
dwTlsIndex = TlsAlloc () ;
This index value can be stored in a global variable or passed to the Thread function in the argument structure.
The Thread function begins by allocating memory for the data structure and calling TlsSetValue using the index obtained above:
TlsSetValue (dwTlsIndex, GlobalAlloc (GPTR, sizeof (DATA)) ;
This associates a pointer with a particular thread and a particular thread index. Now any function that needs to use this pointer, including the original Thread function itself, can include code like so:
PDATA pdata ;
pdata = (PDATA) TlsGetValue (dwTlsIndex) ;
Now it can set or use pdata->a and pdata->b. Before the Thread function terminates, it frees the allocated memory:
GlobalFree (TlsGetValue (dwTlsIndex)) ;
When all the threads using this data have terminated, the primary thread frees the index:
TlsFree (dwTlsIndex) ;