Thread Local Storage and Dynamic Link Libraries

< BACK  NEXT >
[oR]

When writing a multithreaded application you not only need to ensure that your code is correctly synchronized, but also that any libraries you call are also written to be multithreaded. If your code calls functions in a Dynamic Link Library that are designed only for single-threaded calls, you can run into deadlock, race conditions, and other synchronization problems.

If you know that a library is single-threaded, you need to ensure that you always call functions in that library on the same thread. That way, two threads will not be actively calling functions in the library at the same time this is called serialization.

When writing a library yourself you need to decide whether you want it to be single- or multithreaded. A library written to be multithreaded must use the synchronization techniques described in previous sections this makes it thread-safe. If you are writing a thread-safe library, you may have to use Thread Local Storage (TLS) to manage global variables. Consider the situation of the errno variable in the C run-time library. This variable contains the last error number encountered when calling a C run-time function such as fopen. In a multithreaded application, two threads may call fopen at much the same time, and if there is only a single errno variable, one thread could end up using the return result from the other thread's fopen call (Figure 6.7). This can happen if a multithreaded application calls the single-threaded C run-time library. The solution is for each thread to have its own copy of errno. This means that when the thread is created, the errno variable must be created, and when the thread terminates, the errno variable must be destroyed. Windows CE provides Thread Local Storage (TLS) to solve this problem. TLS is most often used in Dynamic Link Libraries, but it can also be used in EXEs.

Figure 6.7. Single-threaded C run-time calls from a multithreaded application
graphics/06fig07.gif

When an application starts up, Windows CE creates 64 slots into which a DWORD value can be stored for each thread in that application. A DLL can reserve one of these slots for its own use by calling TlsAlloc. This function then returns the next available slot number. The function TlsSetValue can be used to store a DWORD value into a slot for the thread on which TlsSetValue is called, and the function is passed the slot number that was returned from TlsAlloc. Note that separate DWORD values can be stored for each thread in an application using this technique. At some later stage, the thread can call TlsGetValue to retrieve the value held in the given slot for that thread. Finally, when the slot is finished with, TlsFree is called and is passed the slot number returned from calling TlsAlloc.

Slots are in quite short supply, so a DLL will typically only request a single slot by calling TlsAlloc. If the thread needs to store more than a single DWORD, it will generally use dynamic memory allocation (see Chapter 12), and store a pointer to this memory using TlsSetValue. DLLs receive notification of when a thread is created in the application the DLL is mapped into Windows CE calls the DLL's DllMain function, passing the DLL_THREAD_ATTACH value in the fdwReason parameter. Likewise, a DLL is notified when a thread terminates with the fdwReason parameter set to DLL_THREAD_DETACH.

The code in Listing 6.5 shows how to use TLS for a DLL that needs to maintain a string buffer for each thread calling into the library. The code shows an implementation of DllMain, and this function is called when

  • fdwReason = DLL_PROCESS_ATTACH: The DLL is first loaded by the process.

  • fdwReason = DLL_THREAD_ATTACH: A thread is created by any code in the process, not just threads created by the DLL.

  • fdwReason = DLL_THREAD_DETACH: A thread in the process terminates.

  • fdwReason = DLL_PROCESS_DETACH: The process terminates.

A global variable g_dwTlsIndex is declared, and this will contain the slot number for this DLL obtained from calling TlsAlloc when the DLL is first loaded. The function TlsAlloc returns the value TLS_OUT_OF_INDEXES if no more slots are available. Returning FALSE from DllMain when the reason code is DLL_PROCESS_ATTACH causes the loading of the DLL to fail.

Listing 6.5 Using TLS data in a Dynamic Link Library
 DWORD g_dwTlsIndex = TLS_OUT_OF_INDEXES; BOOL WINAPI DllMain(HINSTANCE hinstDLL,         DWORD fdwReason, LPVOID fImpLoad) {   LPTSTR lpszStr;   switch(fdwReason) {   case DLL_PROCESS_ATTACH:     g_dwTlsIndex = TlsAlloc();     if(g_dwTlsIndex == TLS_OUT_OF_INDEXES)       return (FALSE);     break;   case DLL_THREAD_ATTACH:       break;   case DLL_THREAD_DETACH:     if(g_dwTlsIndex != TLS_OUT_OF_INDEXES)     {       lpszStr =         (LPTSTR)TlsGetValue(g_dwTlsIndex);       if(lpszStr != NULL)         HeapFree(GetProcessHeap(),           0, lpszStr);     }     break;   case DLL_PROCESS_DETACH:     if(g_dwTlsIndex != TLS_OUT_OF_INDEXES)     {       lpszStr =         (LPTSTR)TlsGetValue(g_dwTlsIndex);       if(lpszStr != NULL)         HeapFree(GetProcessHeap(),           0, lpszStr);       TlsFree(g_dwTlsIndex);     }     break;   }   return TRUE; } 

The code in Listing 6.5 does not allocate memory when the reason code is DLL_THREAD_ATTACH. It is more efficient to only allocate the memory associated with TLS the first time it is needed. The code in Listing 6.6 shows how a function can determine if memory for this thread has already been allocated. The TlsGetValue function is called and is passed the slot number returned from TlsAlloc. If this value is NULL, the memory has not yet been allocated, so HeapAlloc is called to allocate a buffer of 40 bytes in the default heap. The pointer to this newly allocated memory is stored as TLS using the TlsSetValue function. Remember that this allocation will occur for each thread that calls FunctionX.

Listing 6.6 Allocating TLS data for a thread
 LPTSTR FunctionX() {   LPTSTR lpszStr = (LPTSTR)TlsGetValue(g_dwTlsIndex);   if(lpszStr == NULL)   {     lpszStr = (LPTSTR)HeapAlloc(GetProcessHeap(),         0, 40);     TlsSetValue(g_dwTlsIndex, lpszStr);   }   // Now use lpszStr in some way... } 

The memory allocated and stored in TLS must be freed when the thread terminates. This is done when DllMain is called with the reason code DLL_THREAD_DETACH. Since DllMain is called using the thread that is being terminated, calling TlsGetValue will return the DWORD associated with the terminating thread. This code (from Listing 6.5) gets data associated with the slot and thread, and if this is non-null, frees the data.

 case DLL_THREAD_DETACH:   if(g_dwTlsIndex != TLS_OUT_OF_INDEXES)   {     lpszStr =       (LPTSTR)TlsGetValue(g_dwTlsIndex);     if(lpszStr != NULL)       HeapFree(GetProcessHeap(),         0, lpszStr);   }   break; 

When the process unloads the DLL, the slot number has to be freed. In Listing 6.5 a check is also made to see if the data associated with the thread being used to unload the library has been freed, then a call is made to TlsFree to free the slot.

 case DLL_PROCESS_DETACH:   if(g_dwTlsIndex != TLS_OUT_OF_INDEXES)   {     lpszStr =       (LPTSTR)TlsGetValue(g_dwTlsIndex);     if(lpszStr != NULL)       HeapFree(GetProcessHeap(),         0, lpszStr);     TlsFree(g_dwTlsIndex);   }   break; 

The technique described above for TLS is called "dynamic TLS", since memory is allocated and de-allocated dynamically for each thread. In Windows NT/98/2000, "static TLS" is also supported through the #pragma data_seg compiler directive. Any variable declarations placed between #pragma data_ seg compiler directives will be duplicated for each thread. Static TLS is not supported in Windows CE.

As an aside, Windows CE 3.0 now supports the DisableThreadLibraryCalls function. Calling this function disables DllMain being called with the reason codes DLL_THREAD_ATTACH and DLL_THREAD_DETACH. The function takes a single argument which is the DLL's module or instance handle. This can reduce code size and, for processes that create large number of threads, improve performance. Of course, you don't want to call DisableThreadLibraryCalls for a DLL that implements TLS using the techniques described here. The best place to DisableThreadLibraryCalls is in DLLMain when the reason code is DLL_PROCESS_ATTACH.

 case DLL_PROCESS_ATTACH:   DisableThreadLibraryCalls(hinstDLL);   break; 

< BACK  NEXT >


Windows CE 3. 0 Application Programming
Windows CE 3.0: Application Programming (Prentice Hall Series on Microsoft Technologies)
ISBN: 0130255920
EAN: 2147483647
Year: 2002
Pages: 181

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net