Search code examples
c++windowsthread-local-storage

How to release the object in a TLS-slot at thread exit on Windows?


for example, in a multi-thread program:

struct TLSObject;

void foo()
{
    TLSObject* p = TlsGetValue(slot);
    if (p == 0) {
        p = new TLSObject;
        TlsSetValue(slot, p);
    }
    // doing something with p
}

the first time to call foo() in any thread will makes a new TLSObject.

my question is: How to delete a TLSObject(if I don't use boost::thread and boost::thread_specific_ptr) ?

boost::thread_specific_ptr can do cleanup work at thread exit, but it depends on boost::thread I guess, not for normal OS thread, and it's slow.


Solution

  • Alright. For Windows Vista and above, as James McNellis said - we could use FlsCallback.

    For a DLL, we could just use DllMain, if reason parameter equals to DLL_THREAD_DETACH, we do the cleanup. An alternative might be to use _pRawDllMain, it's just like another DllMain, you could find it from boost source.

    For an EXE, we could use TLS callback, please have a look at here and here, and, of course, boost source. In practice, it works on Windows XP, but I found that optimizations may make it ineffective, so be careful with optimizations, or make a explicit reference to the pointer of your callback function.

    Save the code below to tls.cpp and add it to your project, no matter it's exe or dll, it will work. Note that for a DLL on Windows Vista and above the onThreadExit function may be called twice - one from dll_callback and one from tls_callback.

    #include <windows.h>
    
    extern void onThreadExit();
    
    static void NTAPI tls_callback(PVOID, DWORD reason, PVOID)
    {
        if (reason == DLL_THREAD_DETACH) {
            onThreadExit();
        }
    }
    
    static BOOL WINAPI dll_callback(LPVOID, DWORD reason, LPVOID)
    {
        if (reason == DLL_THREAD_DETACH) {
            onThreadExit();
        }
        return TRUE;
    }
    
    #pragma section(".CRT$XLY",long,read)
    extern "C" __declspec(allocate(".CRT$XLY")) PIMAGE_TLS_CALLBACK _xl_y = tls_callback;
    
    extern "C"
    {
        extern BOOL (WINAPI * const _pRawDllMain)(HANDLE, DWORD, LPVOID) = &dll_callback;
    }
    
    #pragma comment(linker, "/INCLUDE:__tls_used")
    #pragma comment(linker, "/INCLUDE:__xl_y")
    

    If you think it's obscure, use boost's at_thread_exit, the complexity is hidden. In fact the code above is a simplified version of boost tls. And if you do not want to use boost, on Windows system this is a alternative.

    Or, a more generic way: thread_local.