Per Thread Singleton

Introduction

Sometimes we want our singleton to be per thread instance, to avoid contention in using the objects in threads. Per thread singleton is usually implemented, using a Thread Local Storage(TLS). Per thread singleton too uses a static member to store its information, like normal singleton. Per thread singleton which is implemented, using a Thread Local Storage(TLS), like this one(code listing is shown below) from Dr Dobbs Journal, has a serious bug because it uses the static integer member to store the TLS slot index. And for any thread, the TLS slot index is not always the same for all threads if the thread has code or classes which also make use of the TLS to store information before the singleton instance allocates a TLS slot.

Code Listing from Dr Dobbs Journal

#include <windows.h>
#include <iostream.h>

// This class ensures that the thread specific instance gets 
// deleted as each thread exits.
class ThreadSingletonDestroyer
{
public:
    ~ThreadSingletonDestroyer();
};

// This class implements the functionality needed for a thread
// specific Singleton class.
class ThreadSingleton
{
public:
    static ThreadSingleton* GetObject();
    DWORD GetThreadID()
    {
        return m_ThreadID;
    }

protected:
    ThreadSingleton(DWORD threadID)
    {
        m_ThreadID = threadID;
    }

private:
    static DWORD m_ThreadIndex; // static TLS slot index
    DWORD  m_ThreadID;
    static void DestroyObject();

    
    friend ThreadSingletonDestroyer;
};


DWORD ThreadSingleton :: m_ThreadIndex = -1;

ThreadSingletonDestroyer :: ~ThreadSingletonDestroyer()
{
    // Just call the function DestroyObject.
    ThreadSingleton :: DestroyObject();
}

void ThreadSingleton :: DestroyObject()
{
    // If the thread Index is not even initialized yet, it 
    // means there have been no object created for this class 
    // yet, just return.
    if(-1 == m_ThreadIndex)
    {
        return;
    }

    ThreadSingleton *obj = NULL;

    // If there is an object at the TLS index, delete it, 
    // otherwise just return.
    obj = (ThreadSingleton*)TlsGetValue(m_ThreadIndex);

    if(NULL != obj)
    {
        delete obj;
    }
}

ThreadSingleton* ThreadSingleton :: GetObject()
{
    ThreadSingleton *retVal = NULL;

    // This "if" block needs to be protected by a 
    // CRITICAL_SECTION, left out for sake of clarity.
    if(-1 == m_ThreadIndex)
    {
        m_ThreadIndex = TlsAlloc();
        if(-1 == m_ThreadIndex)
        {
            cout << "Error while calling TlsAlloc\n";
            return NULL;
        }
    }

    // Try to get an object at the TLS index, but if we can't 
    // then create one and put in the TLS location.
    retVal = (ThreadSingleton*)TlsGetValue(m_ThreadIndex);

    if(NULL == retVal)
    {
        retVal = new ThreadSingleton(GetCurrentThreadId());
        
        TlsSetValue(m_ThreadIndex, retVal);
    }

    return retVal;
}


// This is the thread function, this function is called to 
// execute the thread created in "main".
DWORD WINAPI ThreadMain(void*)
{
    // This object's destructor will destroy the thread 
    // specific singleton instance, upon the thread's exit.
    ThreadSingletonDestroyer tsDestroyer;

    ThreadSingleton *obj = ThreadSingleton :: GetObject();

    cout <<"The thread ID is = " << obj->GetThreadID() << endl;

    return 0;
}

int main()
{
    ThreadSingletonDestroyer tsDestroyer;
    DWORD dwThreadID;

    ThreadSingleton *obj = ThreadSingleton :: GetObject();

    // Print the thread ID.
    cout <<"The thread ID is = " << obj->GetThreadID() << endl;

    HANDLE handle = CreateThread(NULL, 0, ThreadMain, NULL, 0, 
                                 &dwThreadID);

    if(NULL == handle)
    {
        cout << "Error while creating a thread!\n";
        return -1;
    }

    (void)WaitForSingleObject(handle, INFINITE);

    return 0;
}

I have chosen to make my per thread singleton, using a dictionary as a static member instead. The dictionary has the thread ID as the key and the templatized/generic object as value.

During accessing the singleton for the first time in the thread, the singleton will check that the thread ID does not exist in the dictionary and will attempt to instantiate the object and store it in the dictionary for future retrieval before returning object. For subsequent retrieval, it just fetches the object from the dictionary using the thread ID.

C++ Per Thread Singleton

template< typename T >  
class PerThreadSingleton      
{  
private:  
    PerThreadSingleton() {} // hide ctor  
    ~PerThreadSingleton() {} // hide dtor  
    PerThreadSingleton(PerThreadSingleton& that) {} // hide copy ctor  
    void operator=(PerThreadSingleton& that) {} // hide asst optor  
public:  
    static T* Instance()  
    {  
        CritSectLock lock(m_cs.GetInnerCritSect());  
        DWORD nThreadID = GetCurrentThreadId();  
        T* retVal;  
        if(m_map.find(nThreadID)!=m_map.end())  
        {  
            retVal = m_map[nThreadID];  
        }  
        else  
        {  
            retVal = new T();  
            m_map[nThreadID] = retVal;  
        }  
        return retVal;  
    }  
    //! Deletes the static instance immediately  
    static void DeleteInstance()  
    {  
        CritSectLock lock(m_cs.GetInnerCritSect());  
        DWORD nThreadID = GetCurrentThreadId();  
        T* retVal;  
        if(m_map.find(nThreadID)!=m_map.end())  
        {  
            retVal = m_map[nThreadID];  
            m_map.erase(nThreadID);  
            delete retVal;   
        }  
    }  
private:  
    static CritSect m_cs;  
    static std::map<DWORD, T*> m_map;  
};  
template<typename T> CritSect PerThreadSingleton<T>::m_cs;  
template<typename T> std::map<DWORD, T*> PerThreadSingleton<T>::m_map;  

Example on how to use the C++ Per Thread Singleton

int main()  
{  
    PerThreadSingleton<classA>::Instance()->DoWork;  
      
    // delete the instance after use.  
    PerThreadSingleton<classA>::DeleteInstance();  
    return 0;  
}  

Per Thread Singleton

C# Per Thread Singleton

public class PerThreadSingleton < T > where T   
: class // T must be a class type  
, new() // T must have a default ctor     
{  
    static PerThreadSingleton()   
    {   
        m_SyncObj = new Object();   
        m_Dictonary = new Dictionary<Int32, T>();  
    }  
    ~PerThreadSingleton()  
    {  
        Int32 nThreadID = Thread.CurrentThread.ManagedThreadId;  
        Debug.Print("PerThreadSingleton Finalizer: {0}", nThreadID);  
    }  
    private PerThreadSingleton() { } // hide ctor  
    private PerThreadSingleton(PerThreadSingleton<T> that) {} // hide copy ctor  
    private PerThreadSingleton<T> op_Assign(PerThreadSingleton<T> that)   
    { return null; } // hide asst optor  
    public static T Instance  
    {  
    get  
    {  
            lock (m_SyncObj)  
            {  
                T retVal;  
                Int32 nThreadID = Thread.CurrentThread.ManagedThreadId;  
                if (m_Dictonary.ContainsKey(nThreadID))  
                {  
                    return m_Dictonary[nThreadID];  
                }  
                else  
                {  
                    retVal = new T();  
                    m_Dictonary[nThreadID] = retVal;  
                }  
                return retVal;  
            }  
        }  
        // no set  
    }  
    public static void DeleteInstance()  
    {  
        lock (m_SyncObj)  
        {  
            Int32 nThreadID = Thread.CurrentThread.ManagedThreadId;  
            if (m_Dictonary.ContainsKey(nThreadID))  
            {  
                m_Dictonary.Remove(nThreadID);  
            }  
        }  
    }  
    private static System.Object m_SyncObj;  
    private static Dictionary<Int32, T> m_Dictonary;  
}  

Example on how to use the C++ Per Thread Singleton

int DoAction()  
{  
    PerThreadSingleton<classA>.Instance.DoWork();  
      
    // delete the instance after use.  
    // Actually, it only removes the instance   
    // from the dictionary so that it can be  
    // garbage-collected.  
    PerThreadSingleton<classA>.DeleteInstance();  
      
    return 0;  
}  

Notes

One caveat in this design is that you cannot use the per thread singletons in thread pools and the latest task library which comes with Visual Studio 2010 from Microsoft because thread pool and task library share its threads, that is, the thread which is used to execute work items and tasks is not always the same, so their thread IDs will also be different. My design requires on the thread IDs to be consistent throughout the execution of the thread.



About the Author

Wong Shao Voon

I guess I'll write here what I does in my free time, than to write an accolade of skills which I currently possess. I believe the things I does in my free time, say more about me.

When I am not working, I like to watch Japanese anime. I am also writing some movie script, hoping to see my own movie on the big screen one day.

I like to jog because it makes me feel good, having done something meaningful in the morning before the day starts.

I also writes articles for CodeGuru; I have a few ideas to write about but never get around writing because of hectic schedule.

Downloads

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: August 20, 2014 @ 1:00 p.m. ET / 10:00 a.m. PT When you look at natural user interfaces as a developer, it isn't just fun and games. There are some very serious, real-world usage models of how things can help make the world a better place – things like Intel® RealSense™ technology. Check out this upcoming eSeminar and join the panel of experts, both from inside and outside of Intel, as they discuss how natural user interfaces will likely be getting adopted in a wide variety …

  • Protecting business operations means shifting the priorities around availability from disaster recovery to business continuity. Enterprises are shifting their focus from recovery from a disaster to preventing the disaster in the first place. With this change in mindset, disaster recovery is no longer the first line of defense; the organizations with a smarter business continuity practice are less impacted when disasters strike. This SmartSelect will provide insight to help guide your enterprise toward better …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds