Click here to Skip to main content
15,867,568 members
Articles / Programming Languages / C++
Article

Simplify the Use of Thread Local Storage

Rate me:
Please Sign up or sign in to vote.
4.63/5 (10 votes)
14 Apr 20024 min read 131.5K   1.6K   39   21
A template class to simplify and provide type safe access to Thread Local Storage data.

Introduction

How many times have you had to write a thread-safe library that provides some kind of services to client applications? Even today, it is not uncommon to write DLLs and make use of Thread Local Storage to store thread-specific pieces of data.

Win32 provides a complete set of APIs to retrieve and edit pieces of information stored in a part of memory that is specific to a particular thread. Such TLS slots must be allocated and disposed, as new threads access or leave the DLL.

As far as the implementation is concerned, you often have to write the same piece of code over and again. A typical library will probably have a DllMain() entry-point using the following form:

struct ThreadData {
    // some thread specific data
};
...
DWORD g_dwThreadIndex;

extern "C" BOOL WINAPI DllMain(HINSTANCE hInstance, 
                      DWORD dwReason, LPVOID /*pReserved*/)
{
    ThreadData* pData;

    switch (dwReason) {
        case DLL_PROCESS_ATTACH:

            // allocate a TLS index

            g_dwThreadIndex = ::TlsAlloc();
            if (g_dwThreadIndex == TLS_OUT_OF_INDEXES)
                return FALSE;

            //break;        // execute the DLL_THREAD_ATTACH code

        case DLL_THREAD_ATTACH:

            // allocate memory for this thread

            pData = (ThreadData*) ::LocalAlloc(LPTR, sizeof(ThreadData));
            if (pData == 0)
                return FALSE;

            ::TlsSetValue(g_dwThreadIndex, (LPVOID) pData);
            break;

        case DLL_THREAD_DETACH:

            // release memory for this thread

            pData = (ThreadData*) ::TlsGetValue(g_dwThreadIndex);
            if (pData != 0)
                ::LocalFree((HLOCAL) pData);
            break;

        case DLL_PROCESS_DETACH:

            // release memory for this thread

            pData = (ThreadData*) ::TlsGetValue(g_dwThreadIndex);
            if (pData != 0)
                ::LocalFree((HLOCAL) pData);

            // release the TLS index

            ::TlsFree(g_dwThreadIndex);
            break;
    }

    return TRUE;
}

Accessing or modifying the thread data is usually a matter of calling the appropriate TLS functions:

// access the thread specific data
ThreadData* pData = ::TlsGetValue(g_dwThreadIndex);

// modify some information
LPCTSTR szInformation = _T("Some Information");

pData->_size = lstrlen(szInformation);
lstrcpy(pData->_sz, szInformation);
...
::TlsSetValue(g_dwThreadIndex, (LPVOID) pData);

As you see, the code is not particularly difficult to write and understand, but you need to resort to all kinds of type casting to access the right information. Besides, the application needs to expose a global variable g_dwThreadIndex, something developers are learning to avoid as much as possible.

The CThreadData<> Template Class

I'm always trying to avoid using global variables in my C++ projects. I've long learned that this can easily be solved using the singleton design-pattern. Basically, you have a class that enforces a single-instance, thus providing the same kind of services a global variable does. What is more important, is that you can easily encapsulate data getters and setters using appropriate member functions, which give a type-safe means of accessing the required information.

While trying to encapsulate the patterns behind using the Thread Local Storage in my DLLs, I came up with a template class that performs all the grunt work of using the TLS APIs. For a particular application, all I have to do is create a derived class and implement some trivial getters and setters.

#include "ThreadData.h"

struct _ThreadData
{
    DWORD _dwData;
};

class CSampleThreadData : public CThreadData<CSampleThreadData, _ThreadData>
{
// operations
public:
    DWORD GetThreadData(void) const
    {
        _ThreadData const* pData = GetValue();
        return pData->_dwData;
    }

    void SetThreadData(DWORD dwData)
    {
        _ThreadData* pData = GetValue();
        pData->_dwData = dwData;
    }

    DWORD GetProcessData(void) const
        { return m_processData; }

    void SetProcessData(DWORD processData)
        { m_processData = processData; }
...
// additionnal data members
// these are shared across all threads
protected:
    DWORD m_processData;
    ...
};

With the preceding class declaration, the DllMain() code becomes:

extern "C" BOOL WINAPI DllMain(HINSTANCE hInstance, 
                    DWORD dwReason, LPVOID /*pReserved*/)
{
    CSampleThreadData& threadData = CSampleThreadData::Instance();
    if (!threadData)
        return FALSE;

    switch (dwReason) {
        case DLL_PROCESS_ATTACH:
        case DLL_THREAD_ATTACH:
            threadData.AllocData();
            break;

        case DLL_THREAD_DETACH:
            threadData.FreeData();
            break;

        case DLL_PROCESS_DETACH:
            threadData.FreeData();
            break;
    }

    return TRUE;
}

The benefits of using such a construct are manifold. First, you can easily see that maintenance is improved, since the code is encapsulated into one class. Then, there's no need for exposing global variables any longer. What's more important is that access to the data is controlled through type-safe member functions that can perform additional data validation as required. Finally, you can have the singleton class decide which piece of data is thread-specific and which must be shared across all threads.

Accessing and modifying the required information simply becomes:

CSampleThreadData& threadData = CSampleThreadData::Instance();

// accessing thread-specific data
DWORD dwData = threadData.GetThreadData();

// modifying process data
threadData.SetProcessData(dwData);

How does it work?

There are actually two classes that comes when you include threaddata.h.

First, the CThreadDataBase class performs the grunt work of encapsulating the Thread Local Storage API.

class CThreadDataBase
{
// construction / destruction
public:
    CThreadDataBase()
    {
        m_dwThreadIndex = ::TlsAlloc();
    }

    virtual ~CThreadDataBase()
    {
        if (m_dwThreadIndex != TLS_OUT_OF_INDEXES)
            ::TlsFree(m_dwThreadIndex);
    }

// operators
public:
    operator bool(void) const
        { return (m_dwThreadIndex != TLS_OUT_OF_INDEXES); }

    bool operator !(void) const
        { return !operator bool(); }

// operations overrides
public:
    virtual void SetValue(LPVOID pvData)
    {
        _ASSERTE(m_dwThreadIndex != TLS_OUT_OF_INDEXES);
        ::TlsSetValue(m_dwThreadIndex, pvData);
    }

    LPCVOID GetValue(void) const
    {
        _ASSERTE(m_dwThreadIndex != TLS_OUT_OF_INDEXES);
        return ::TlsGetValue(m_dwThreadIndex);
    }

    LPVOID GetValue(void)
    {
        _ASSERTE(m_dwThreadIndex != TLS_OUT_OF_INDEXES);
        return ::TlsGetValue(m_dwThreadIndex);
    }

// data members
protected:
    DWORD m_dwThreadIndex;
};

Then, the CThreadData<> template helps implement the singleton behavior for your derived class T, provides additional members for local memory allocation and de-allocation and provides simple const-aware type-safe getters and setters around the thread-specific piece of data, TData.

template <class T, typename TData>
class CThreadData : public CThreadDataBase
{
// construction / destruction
private:
    CThreadData(CThreadData const&);    // prevent copy construction

protected:
    CThreadData()    // disallow illegal construction
    {}

public:
    virtual ~CThreadData()    // this should be private: VC++ bug!
    {}

// singleton operations
public:
    static T& Instance(void);

// operations
public:
    void AllocData(void)
    {
        _ASSERTE(GetValue() == 0);
        SetValue(new TData());
    }

    void FreeData(void)
    {
        TData* pData = GetValue();
        if (pData != 0) {
            delete pData;
            SetValue(0);
        }
    }

// overrides
public:
    void SetValue(TData* pvData)
        { CThreadDataBase::SetValue(reinterpret_cast<LPVOID>(pvData)); }

    TData const* GetValue(void) const
        { return reinterpret_cast<
                TData const*>(CThreadDataBase::GetValue()); }

    TData* GetValue(void)
        { return reinterpret_cast<TData*>(CThreadDataBase::GetValue()); }

// data members
protected:
    static T*  m_pInstance;
};

template <class T, typename TData> 
         T* CThreadData<T, TData>::m_pInstance = 0;

template <class T, typename TData>
T& CThreadData<T, TData>::Instance(void)
{
    if (m_pInstance == 0) {
        static T instance;
        m_pInstance = &instance;
    }

    return (*m_pInstance);
}

Yet Another Way

Maximilian Häenel pointed out some modifications to the code which, albeit differently, allows for a simpler usage. I have modified his code slightly to accommodate my coding conventions, but all credits go to him (although, I must retain bug ownership, I'm afraid :-) ).

The purpose of this new class is to implement the singleton behavior, as explained in the main section, and delegate the work to the template class TData. This class is responsible for providing corresponding attribute getters and setters. Here is the new template class:

template <typename TData>
class CSimpleThreadData : public CThreadDataBase
{
// construction / destruction
private:
    // prevent copy construction
    CSimpleThreadData(CSimpleThreadData const&);

protected:
    CSimpleThreadData()    // disallow illegal construction
    {}

public:
    // this should be private: VC++ bug!
    virtual ~CSimpleThreadData()
    {}

// singleton operations
public:
    static CSimpleThreadData<TData>& Instance(void);

// allocation / de-allocation
public:
    void AllocData()
    {
        _ASSERTE(GetValue() == 0);
        SetValue(new TData());
    }

    void FreeData(void)
    {
        TData* pData = GetValue();
        if (pData != 0) {
            delete pData;
            SetValue(0);
        }
    }

// attributes
public:
    static TData& Value(void)
    {
        CSimpleThreadData<TData>& threadData = 
              CSimpleThreadData<TData>::Instance();
        return *(threadData.GetValue());
    }

    void SetValue(TData* pvData)
        { CThreadDataBase::SetValue(reinterpret_cast<LPVOID>(pvData)); }

    TData const* GetValue(void) const
        { return reinterpret_cast<TData const*>
                         (CThreadDataBase::GetValue()); }

    TData* GetValue(void)
        { return reinterpret_cast<TData*>(CThreadDataBase::GetValue()); }

// data members
protected:
    static CSimpleThreadData*  m_pInstance;
};

template <typename TData> CSimpleThreadData<
       TData>* CSimpleThreadData<TData>::m_pInstance = 0;

template <typename TData>
CSimpleThreadData<TData>& CSimpleThreadData<TData>::Instance(void)
{
    if (m_pInstance == 0) {
        static CSimpleThreadData<TData> instance;
        m_pInstance = &instance;
    }

    return (*m_pInstance);
}

If you wanted to provide thread-specific information to your dynamic link library, you could for instance, provide the following implementation:

class _ThreadInfo
{
// construction / destruction
public:
    _ThreadInfo()
        : m_dwData(0)
        {}

    virtual ~_ThreadInfo()
        {}

// operations
public:
    DWORD GetData(void) const
        { return m_dwData; }

    void SetData(DWORD dwData)
        { m_dwData = dwData; }

// data members
protected:
    DWORD m_dwData;
};

typedef CSimpleThreadData<_ThreadInfo>    CSimpleThreadInfo;

With this code, the implementation of the DllMain() function remains unchanged. As for accessing the thread-specific data, here goes the code:

_ThreadInfo& threadInfo = CSimpleThreadInfo::Value();

DWORD dwData = threadInfo.GetData();
threadInfo.SetData(dwData);

Using this class or the other is a matter of taste. I think it is a nice concept to try and encapsulate the use of the Thread Local Storage API, even if that can be done with many different methods.

The code for the sample application has been augmented to use both versions, so that you can make your own decisions. Simply compile with the __USES_SIMPLE_THREADDATA preprocessor define, to enable this alternative version.

Conclusion

This article illustrates the use of a couple of template classes that provides an alternative way of using the Thread Local Storage API inside your dynamic link libraries. I hope that you will find these classes useful in your own projects and am looking forward to hearing your thoughts and suggestions.

Article updates

The code for this article has been updated to accommodate for compilation errors. A new class has been added, thanks to the suggestion from Maximilian Häenel (see forum for the related discussion). Finally, the text for the article has been modified to reflect the corresponding changes.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
France France
Coming soon...

Comments and Discussions

 
GeneralRe: Modification Pin
Maxime Labelle13-Apr-02 20:50
Maxime Labelle13-Apr-02 20:50 
GeneralRe: Modification Pin
Maxime Labelle13-Apr-02 21:06
Maxime Labelle13-Apr-02 21:06 
GeneralRe: Modification Pin
Maximilian Hänel14-Apr-02 1:47
Maximilian Hänel14-Apr-02 1:47 
GeneralRe: Modification Pin
Maxime Labelle14-Apr-02 20:00
Maxime Labelle14-Apr-02 20:00 
GeneralRe: Modification Pin
Maxime Labelle14-Apr-02 23:30
Maxime Labelle14-Apr-02 23:30 
GeneralRe: Modification Pin
Maximilian Hänel15-Apr-02 0:49
Maximilian Hänel15-Apr-02 0:49 
GeneralRe: Modification Pin
Maxime Labelle14-Apr-02 23:22
Maxime Labelle14-Apr-02 23:22 
QuestionWhy using ::LocalAlloc()? Pin
Maximilian Hänel13-Apr-02 14:36
Maximilian Hänel13-Apr-02 14:36 
AnswerRe: Why using ::LocalAlloc()? Pin
Maxime Labelle13-Apr-02 21:08
Maxime Labelle13-Apr-02 21:08 
AnswerRe: Why using ::LocalAlloc()? Pin
Maxime Labelle14-Apr-02 23:13
Maxime Labelle14-Apr-02 23:13 
GeneralRe: LocalAlloc() - it's in kernel32 (!) Pin
pg--az23-Feb-07 20:46
pg--az23-Feb-07 20:46 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.