代码之家  ›  专栏  ›  技术社区  ›  VoidPointer

线程本地单件

  •  4
  • VoidPointer  · 技术社区  · 16 年前

    我想创建一个单例类,在使用它的每个线程中实例化一次。我想将实例指针存储在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 回复  |  直到 9 年前
        1
  •  11
  •   DavidK    16 年前

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

    编辑: 接下来是另外两个问题…我不明白为什么使用tlsalloc/tlsgetvalue等不能如您所期望的那样工作。因为保存指向singleton的指针的内存只能被相关线程访问,所以延迟初始化它不会有任何问题。但是没有明确的回调接口来清理它们。

    最明显的解决方案是让所有线程主函数调用一个方法来清除创建的单例(如果有的话)。

    如果线程很可能会创建一个singleton,一个更简单的模式可能是在线程主函数的开头创建singleton,然后在末尾删除它。然后,您可以通过在堆栈上创建单例来使用raii,或者将其保存在std::auto_ptr<gt;中,以便在线程结束时将其删除。(除非线程异常终止,但如果发生这种情况,所有的赌注都将被取消,并且泄漏的对象是您的问题中最小的一个。)然后,如果线程的大部分功能都在一个类中,您可以绕过单例,或者将其存储在TLS中,或者将其存储在类的成员中。

        2
  •  4
  •   sbi    16 年前

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

        3
  •  1
  •   iain    16 年前

    我们使用一个存储线程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的平台上工作。注意,关键部分仅用于返回/创建引用,一旦获得引用,所有调用都在关键部分之外。