Click here to Skip to main content
15,867,686 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

 
GeneralAllocating data as it is required Pin
Paul Vickery7-Jun-07 4:06
professionalPaul Vickery7-Jun-07 4:06 
GeneralRe: Allocating data as it is required Pin
oren.shnitzer12-Mar-11 10:55
oren.shnitzer12-Mar-11 10:55 
GeneralDLLs Pin
MyKillK1-Sep-04 9:57
MyKillK1-Sep-04 9:57 
GeneralRe: DLLs Pin
MyKillK5-Sep-04 11:20
MyKillK5-Sep-04 11:20 
QuestionWhy not just use __declspec(threadlocal) ? Pin
olesxxx5-Jun-04 8:56
olesxxx5-Jun-04 8:56 
Answer__declspec(thread) fails for LoadLibrary DLLs Pin
Lacuna5-Jul-04 6:36
Lacuna5-Jul-04 6:36 
AnswerRe: Why not just use __declspec(threadlocal) ? Pin
waykohler19-Jul-10 10:41
waykohler19-Jul-10 10:41 
GeneralVirtuals Pin
Anonymous11-May-04 4:29
Anonymous11-May-04 4:29 
QuestionWhy the m_pInstance pointer ? Pin
Matthias Mann15-Apr-02 11:22
Matthias Mann15-Apr-02 11:22 
GeneralModification Pin
Maximilian Hänel13-Apr-02 15:59
Maximilian Hänel13-Apr-02 15:59 
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 
Hi Maxime,

Maxime Labelle wrote:
I have some comments on your suggestion.
yw Smile | :)

Maxime Labelle wrote:
First, your implementation is a singleton class per se.
However, you can't easily add member variables and extra
behaviour to this class. When you derive a new class from
your CThreadData, the derived class is not
really a singleton. For instance, what if you've got two
derived classes. They both share one single static
instance, thus yielding incorrect behaviour.


That's right. But why should one want to derive from CThreadData?
The data and data accessor functions are all in the TData class. So if you want to add additional ptd, you add it there. Actually there are no restrictions on how you implement the TData class. So instead of deriving from CThreadData to add more functionalitiy, you would derive from your TData implementation (or create a new class).
However, if for what ever reason you actually want to derive from CThreadData, then my implementation fails - as you mentioned. Blush | :O

The main difference beetween our implementations is, that your impl requires to derive from CThreadData (as long as you don't write
typedef CThreadData<CThreadData, _ThreadData> CSampleThreadData;
) whereas my impl doesn't allow to dervie from Big Grin | :-D

So let's put them together... Smile | :)

cheers

Max
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.