A Per-Thread Singleton Class

A refreshing look at an old pattern.


May 01, 2002
URL:http://drdobbs.com/a-per-thread-singleton-class/184401516

May 2002/A Per-Thread Singleton Class


Introduction

A Singleton class is used when there must be exactly one instance of a class, and it must be accessible to all users from a well-known access point. To ensure that exactly one instance of a class is created, the class itself is made responsible for keeping track of its sole instance. Most Singleton classes ensure this by intercepting requests to create new objects. Whenever there is a request for an instance of a class, the class checks to see if an instance already exists. If the instance exists, then the class returns it, else a new instance is created, which is returned in response to the current and subsequent requests.

Need for a Per-Thread Singleton

The traditional Singleton class allows exactly one instance per process (see sidebar, “How a Traditional Singleton Class Works”), but in the multithreaded world of today, often you need an instance of a class for every thread in the process. For example, many Windows NT services are implemented as a server in a client-server environment. Many of these services create a separate thread to process an incoming request from a client. In most of these implementations, the different threads can run completely independent of each other. Usually each of these threads creates one or more objects, which contain the context information specific to the thread. Examples of the contents of these objects may be sockets on which the thread communicates with the client, thread-specific logging information, etc. Most of the functions that are executed as part of the thread execution need access to these objects in order to do their job. Short of passing these objects as a parameter to every function, you need a way to allow all the functions to access these objects. In a multithreaded environment, it is not possible to have this object as a global variable. A traditional Singleton class also does not work here, as you need multiple instances per process and, to be precise, a single instance per thread. This is where a per-thread Singleton class can be helpful.

Per-Thread Singleton Attributes

A thread-specific Singleton class should have the following properties:

The first two properties are similar to those of a traditional Singleton class. The third is usually not an issue for the traditional Singleton class since the solitary instance is created and stored as a static member variable in the class, which goes away when the program exits. However, the same strategy doesn’t work for a thread-specific Singleton class. Because of the way the per-thread Singleton class is implemented, the thread-specific instance needs to be explicitly destroyed (more on this later).

Ensuring One Instance per Thread

You need a way to store instances of the class on a per thread basis. You cannot store them as static members as there is only one copy of a static member per process. The Windows NT/2000 operating system has a really cool feature called TLS (Thread Local Storage) that can help here (see the sidebar, “TLS”). TLS is a method by which each thread in a multithreaded process may allocate locations in which to store thread-specific data. These locations are referenced by TLS indexes, which are unique in a process. Using the same index in different threads in a process, you can retrieve data that is local to each of the threads.

Global Point of Access

Similar to the traditional Singleton classes, the per-thread Singleton classes provide a GetObject type function, which is the only publicly available interface to access an instance of the class. GetObject takes care of returning the thread-specific instance of the class to the caller.

Implementation

Listing 1 shows an implementation of a per-thread Singleton class. The implementation uses Win32 APIs for TLS. This class looks similar to the traditional Singleton class and has the following things in common:

Although this class also has a static member variable like the traditional Singleton class, the purpose of this member is very different. This variable stores the TLS index, which can be used to retrieve the thread-specific instance of this class. Whenever you need an class object, you can call the function ThreadSingleton :: GetObject. This function checks the TLS index to see if an instance of the class already exists for the current thread. If the instance exists, this function just returns the instance; else it creates a class object, stores it in the TLS index, and returns the newly created object. Any further calls to get the object just retrieve the object from the TLS index and return that instance. This way, functions executing in different threads can all access an instance of the object created for their own thread.

As shown in Listing 1, there are two threads:

  1. The main thread, which gets created when the function main is entered.
  2. The second thread, which is created using the Win32 API CreateThread inside the function main.

Both the threads create an instance of the ThreadSingleton class using the member function GetObject. You can see that both the threads get their own copy of the ThreadSingleton object by the different thread IDs returned by the function GetThreadID.

Destroying the Per-Thread Instance

When the thread exits, the instance stored in the TLS area doesn’t get deleted automatically. This instance needs to be explicitly destroyed. To destroy the instance, the class provides another static member function, ThreadSingleton :: DestroyObject. To destroy the thread-specific instance, you need to ensure that this function gets called, exactly once, at the end when the thread is exiting. The easiest way to do this is to declare an object of a class that I call ThreadSingletonDestroyer at the top of the ThreadMain function. You’ll make use of the property that a thread exits whenever ThreadMain exits and that the exit of ThreadMain results in calling the destructor of the ThreadSingletonDestroyer object declared in ThreadMain. To destroy the thread-specific instance, you will call the function ThreadSingleton :: DestroyObject in the destructor of the ThreadSingletonDestroyer class, thereby ensuring that the thread-specific instance is automatically deleted when the thread exits (see Listing 1). DestroyObject is a private function of the ThreadSingleton class, preventing anybody other than the ThreadSingletonDestroyer class, which is a friend, from calling it.

Conclusion

The per-thread Singleton class provides a convenient way to access thread-specific data. No longer do you need to pass thread-specific context data to all the functions that need it; the data is just one well-known function call away. The ThreadSingleton class interface is very similar to the traditional Singleton class interface making it easy to use and understand. A benefit of the class is that it also hides operating-system-specific mechanisms to access thread-specific data. For example, many operating systems support thread-specific storage, but the usage is always different. With this class, it is easy to tailor the implementation of the class to use operating-system-specific ways to store and retrieve thread-specific data, while the interface remains the same and the caller does not need to change.

Puneesh Chaudhry is a principal software engineer for EMC Corporation in Milford, MA. He has a B.E. in Computer Science from Delhi College of Engineering. His interests include backup and other storage technologies. He can be reached at [email protected].

May 2002/A Per-Thread Singleton Class/Listing 1

Listing 1: Per-thread Singleton class implementation

#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;
    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;
}
— End of Listing —
May 2002/A Per-Thread Singleton Class/Sidebar

How a Traditional Singleton Class Works


As the name indicates, the traditional Singleton class allows exactly one instance of the class to be created. The class ensures this by assuming the responsibility to regulate the creation of its objects. The class makes its constructor private, disallowing the direct creation of its objects. In addition, the class defines a static member function that must be called to get an instance of the class. The class also has a static member that stores the pointer to the sole instance of the class. A sample class is shown below:

class Singleton
{
public:
static Singleton* GetObject();
protected:
         Singleton();
private:
static Singleton *m_Instance;
}

The only way to get access to an object of the class is to call the static member function GetObject. When called, GetObject first checks if an instance of the class exists, by checking if m_Instance is NULL. If m_Instance is NULL, then this function creates the first and only instance of the class Singleton, assigns it to m_Instance, and returns it. Subsequent calls to GetObject just return the value stored in m_Instance, ensuring that only one instance of the class is ever created.

May 2002/A Per-Thread Singleton Class/Sidebar

TLS


TLS is a Win32 mechanism that allows multiple threads of a process to store and retrieve data that is unique for each thread. Any one thread allocates an index, and then this index is available for use by all the threads in the process. Each thread in the process has its own TLS slot for this index where a 32-bit pointer to data can be stored and retrieved from. TLS is used as follows:

  1. Use the Win32 API TlsAlloc to allocate a TLS index.
  2. In a thread that needs to use TLS storage, allocate dynamic storage and then use the Win32 API TlsSetValue to associate the index with a pointer to the dynamic storage.
  3. In the same thread, when any piece of code wants to retrieve the data structure stored in the specified TLS index, use the Win32 API TlsGetValue, which retrieves the pointer stored using TlsSetValue.
  4. Use the Win32 API TlsFree to free the index when all the threads are done using the index. In addition, each thread must free the dynamic storage it allocated to associate with the TLS index.

The APIs TlsSetValue and TlsGetValue are designed for extremely fast storage and retrieval. As a result, these APIs do minimal parameter checking. For example, as long as the index specified in these APIs is less than the value TLS_MINIMUM_AVAILABLE, these APIs succeed, even though the index may not have been allocated. It is up to the programmer to ensure that the index is valid.

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.