The Windows Processes and Threads 8



Thread Local Storage


All threads of a process share its virtual address space. The local variables of a function are unique to each thread that runs the function. However, the static and global variables are shared by all threads in the process. With thread local storage (TLS), you can provide unique data for each thread that the process can access using a global index. One thread allocates the index, which can be used by the other threads to retrieve the unique data associated with the index. The constant TLS_MINIMUM_AVAILABLE defines the minimum number of TLS indexes available in each process. This minimum is guaranteed to be at least 64 for all systems. The maximum number of indexes per process is 1,088.



When the threads are created, the system allocates an array of LPVOID values for TLS, which are initialized to NULL. Before an index can be used, it must be allocated by one of the threads. Each thread stores its data for a TLS index in a TLS slot in the array. If the data associated with an index will fit in an LPVOID value, you can store the data directly in the TLS slot. However, if you are using a large number of indexes in this way, it is better to allocate separate storage, consolidate the data, and minimize the number of TLS slots in use. The following diagram illustrates how TLS works.


Windows Thread Local Storage (TLS) illustration


The process has two threads, Thread 1 and Thread 2. It allocates two indexes for use with TLS, gdwTlsIndex1 and gdwTlsIndex2. Each thread allocates two memory blocks (one for each index) in which to store the data, and stores the pointers to these memory blocks in the corresponding TLS slots. To access the data associated with an index, the thread retrieves the pointer to the memory block from the TLS slot and stores it in the lpvData local variable.

It is ideal to use TLS in a dynamic-link library (DLL).


Creating Windows in Threads


Any thread can create a window. The thread that creates the window owns the window and its associated message queue. Therefore, the thread must provide a message loop to process the messages in its message queue. In addition, you must use MsgWaitForMultipleObjects() or MsgWaitForMultipleObjectsEx() in that thread, rather than the other wait functions, so that it can process messages. Otherwise, the system can become deadlocked when the thread is sent a message while it is waiting. The AttachThreadInput() function can be used to allow a set of threads to share the same input state. By sharing input state, the threads share their concept of the active window. By doing this, one thread can always activate another thread's window. This function is also useful for sharing focus state, mouse capture state, keyboard state, and window Z-order state among windows created by different threads whose input state is shared.


Terminating a Thread


Terminating a thread has the following results:


  1. Any resources owned by the thread, such as windows and hooks, are freed.
  2. The thread exit code is set.
  3. The thread object is signaled.
  4. If the thread is the only active thread in the process, the process is terminated.


The GetExitCodeThread() function returns the termination status of a thread. While a thread is executing, its termination status is STILL_ACTIVE. When a thread terminates, its termination status changes from STILL_ACTIVE to the exit code of the thread. When a thread terminates, the state of the thread object changes to signaled, releasing any other threads that had been waiting for the thread to terminate. When a thread terminates, its thread object is not freed until all open handles to the thread are closed.


How Threads are Terminated


A thread executes until one of the following events occurs:


  1. The thread calls the ExitThread() function.
  2. Any thread of the process calls the ExitProcess() function.
  3. The thread function returns.
  4. Any thread calls the TerminateThread() function with a handle to the thread.
  5. Any thread calls the TerminateProcess() function with a handle to the process.



The exit code for a thread is either the value specified in the call to ExitThread(), ExitProcess(), TerminateThread(), or TerminateProcess(), or the value returned by the thread function. If a thread is terminated by ExitThread(), the system calls the entry-point function of each attached DLL with a value indicating that the thread is detaching from the DLL (unless you call the DisableThreadLibraryCalls() function). If a thread is terminated by ExitProcess(), the DLL entry-point functions are invoked once, to indicate that the process is detaching. DLLs are not notified when a thread is terminated by TerminateThread() or TerminateProcess(). The TerminateThread() and TerminateProcess() functions should be used only in extreme circumstances, since they do not allow threads to clean up, do not notify attached DLLs, and do not free the initial stack. In addition, handles to objects owned by the thread are not closed until the process terminates. The following steps provide a better solution:


  1. Create an event object using the CreateEvent() function.
  2. Create the threads.
  3. Each thread monitors the event state by calling the WaitForSingleObject() function. Use a wait time-out interval of zero.
  4. Each thread terminates its own execution when the event is set to the signaled state (WaitForSingleObject() returns WAIT_OBJECT_0).



< Processes & Threads 7 | Win32 Process & Thread Programming | Win32 Programming | Processes & Threads 9 >