线程本地单例

时间:2009-08-04 10:04:23

标签: c++ multithreading winapi singleton thread-local-storage

我想创建一个在使用它的每个线程中实例化一次的单例类。我想将实例指针存储在TLS插槽中。我已经提出了以下解决方案,但我不确定在涉及线程本地存储时是否对singelton工厂的多线程访问有任何特殊考虑。也许还有一个更好的解决方案来实现线程本地单例。

class ThreadLocalSingleton 
{
    static DWORD tlsIndex;
public:
    static ThreadLocalSingleton *getInstance()
    {
        ThreadLocalSingleton *instance = 
            static_cast<ThreadLocalSingleton*>(TlsGetValue(tlsIndex));
        if (!instance) {
            instance = new ThreadLocalSingleton();
            TlsSetValue(tlsIndex, instance);
        }
        return instance;
    }
};
DWORD ThreadLocalSingleton::tlsIndex = TlsAlloc();

Tls *功能当然是win32特定的,但便携性不是这里的主要问题。您对其他平台的想法仍然很有价值。

主要编辑:我最初询问过在这种情况下使用双重检查锁定。但是正如DavidK指出的那样,无论如何都要在每个线程的基础上创建单例。

剩下的两个问题是:

  1. 是否适合回复TlsGetValue / TlsSetValue以确保每个线程获得一个实例并且每个线程只创建一次实例?

  2. 是否可以注册一个回调,允许我在该线程完成时清理与特定线程关联的实例?

3 个答案:

答案 0 :(得分:11)

由于您的对象是线程本地的,为什么需要锁定来保护它们呢?调用getInstance()的每个线程都将独立于任何其他线程,那么为什么不检查单例是否存在并在需要时创建它?只有在多个线程试图访问相同的单例时才需要锁定,这在您的设计中是不可能的,如上所述。

编辑:继续讨论另外两个问题......我看不出有什么理由说使用TlsAlloc / TlsGetValue等不会像你期望的那样工作。由于保存指向单例的指针的内存只能由相关线程访问,因此对它进行延迟初始化不会有任何问题。但是没有明确的回调接口来清理它们。

显而易见的解决方案是使用一个方法,由所有线程主函数调用,清除创建的单例,如果有的话。

如果线程很可能会创建一个singelton,一个更简单的模式可能是在线程main函数的开头创建单例并在结尾处删除它。然后,您可以通过在堆栈上创建单例,或者将其保存在std :: auto_ptr&lt;&gt;中来使用RAII,以便在线程结束时将其删除。 (除非线程异常终止,但如果发生这种情况,所有投注都会被关闭,而泄漏的对象是您遇到的问题中最少的。)然后,您可以只传递单例,或将其存储在TLS中,或将其存储在一个成员中。 class,如果大多数线程功能都在一个类中。

答案 1 :(得分:4)

查看this paper以了解为什么双重检查锁定一般不起作用(即使它在特殊情况下可能有效)。

答案 2 :(得分:1)

我们使用一个类来存储线程id的映射到数据,以实现我们的线程本地存储。这似乎工作得很好,然后这个类的实例可以放在需要线程本地存储的任何地方。通常,客户端使用实例作为静态私有字段。

以下是代码的粗略轮廓

template <class T>
struct ThreadLocal {
    T & value()
    {
        LockGuard<CriticalSection> lock(m_cs);

        std::map<int, T>::iterator itr = m_threadMap.find(Thread::getThreadID());

        if(itr != m_threadMap.end())
            return itr->second;

        return m_threadMap.insert(
            std::map<int, T>::value_type(BWThread::getThreadID(), T()))
                .first->second;
    }

    CriticalSection     m_cs;
    std::map<int, T>    m_threadMap;
};

然后将其用作

class A {
    // ...

    void doStuff();
private:
   static ThreadLocal<Foo> threadLocalFoo;
};

ThreadLocal<Foo> A::threadLocalFoo;

void A::doStuff() {
    // ...
    threadLocalFoo.value().bar();
    // ...
}

这很简单,可以在任何可以获取线程ID的平台上运行。注意,关键部分仅用于返回/创建引用,一旦引用,所有调用都在临界区之外。