4

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

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. 是否可以注册一个回调,允许我在该线程完成时清理与特定线程关联的实例?

4

3 回答 3

11

既然你的对象是线程本地的,为什么你需要锁定来保护它们呢?每个调用 getInstance() 的线程都将独立于任何其他线程,那么为什么不检查单例是否存在并在需要时创建它呢?仅当多个线程尝试访问同一个单例时才需要锁定,这在您的设计中是不可能的,就像上面那样。

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

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

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

于 2009-08-04T10:08:59.967 回答
4

看看这篇论文,了解为什么双重检查锁定在一般情况下不起作用(即使它可能在特殊情况下起作用)。

于 2009-08-04T10:08:14.977 回答
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 的平台。请注意,关键部分仅用于返回/创建引用,一旦您拥有引用,所有调用都在关键部分之外。

于 2009-08-04T11:04:24.083 回答