简体   繁体   English

线程本地单例

[英]Thread-local singletons

I would like to create a singleton class that is instantiated once in each thread where it is used. 我想创建一个单例类,在每个使用它的线程中实例化一次。 I would like to store the instance pointers in TLS slots. 我想将实例指针存储在TLS插槽中。 I have come up with the following solution but I am not sure whether there are any special considerations with multithreaded access to the singelton factory when thread local storage is involved. 我已经提出了以下解决方案,但我不确定在涉及线程本地存储时是否对singelton工厂的多线程访问有任何特殊考虑。 Maybe there is also a better solution to implement thread local singletons. 也许还有一个更好的解决方案来实现线程本地单例。

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();

The Tls*-functions are of course win32 specific but portability is not the main issue here. Tls *功能当然是win32特定的,但便携性不是这里的主要问题。 Your thoughts concerning other platforms would still be valuable. 您对其他平台的想法仍然很有价值。

Major Edit : I had originally asked about using double-checked locking in this scenario. 主要编辑 :我最初询问在这种情况下使用双重检查锁定。 However as DavidK pointed out, the singletons are to be created on a per thread basis anyway. 然而正如DavidK指出的那样,无论如何都要在每个线程的基础上创建单例。

The two remaining questions are: 剩下的两个问题是:

  1. is it appropriate to reply on TlsGetValue/TlsSetValue to ensure that each thread gets one instance and that the instance is created only once for each thread? 是否适合回复TlsGetValue / TlsSetValue以确保每个线程获得一个实例并且每个线程只创建一次实例?

  2. Is it possible to register a callback that allows me to clean up an instance that was associated with a particular thread when that thread finishes? 是否可以注册一个回调,允许我在该线程完成时清理与特定线程关联的实例?

Since your objects are thread-local, why do you need locking to protect them at all? 由于您的对象是线程本地的,为什么需要锁定来保护它们呢? Each threads that calls getInstance() will be independent of any other thread, so why not just check that the singleton exists and create it if needed? 调用getInstance()的每个线程都将独立于任何其他线程,那么为什么不检查单例是否存在并在需要时创建它? The locking would only be needed if multiple threads tried to access the same singleton, which isn't possible in your design as it is above. 只有当多个线程试图访问相同的单例时才需要锁定,这在您的设计中是不可能的,如上所述。

EDIT: Moving on to the two other questions... I can't see any reason why using TlsAlloc/TlsGetValue etc. wouldn't work as you'd expect. 编辑:转到另外两个问题......我看不出为什么使用TlsAlloc / TlsGetValue等没有任何理由可以按照您的预期运行。 Since the memory holding the pointer to your singleton is only accessible to the relevant thread, there won't be any problems with a lazy initialization of it. 由于保存指向单例的指针的内存只能由相关线程访问,因此对它进行延迟初始化不会有任何问题。 However there's no explicit callback interface to clean them up. 但是没有明确的回调接口来清理它们。

The obvious solution to that would be to have a method that is called by all your thread main functions that clears up the created singleton, if any. 显而易见的解决方案是使用一个方法,由所有线程主函数调用,清除创建的单例,如果有的话。

If it's very likely that the thread will create a singelton, a simpler pattern might be to create the singleton at the start of the thread main function and delete it at the end. 如果线程很可能会创建一个singelton,则更简单的模式可能是在线程main函数的开头创建单例并在结尾处删除它。 You could then use RAII by either creating the singleton on the stack, or holding it in a std::auto_ptr<>, so that it gets deleted when the thread ends. 然后,您可以通过在堆栈上创建单例,或者将其保存在std :: auto_ptr <>中来使用RAII,以便在线程结束时将其删除。 (Unless the thread terminates abnormally, but if that happens all bets are off and a leaked object is the least of your problems.) You could then just pass the singleton around, or store it in TLS, or store it in a member of a class, if most of the thread functionality is in one class. (除非线程异常终止,但是如果发生这种情况,所有投注都会关闭,泄漏的对象是你问题中最少的。)然后你可以只传递单例,或者将它存储在TLS中,或者将它存储在一个成员中。 class,如果大多数线程功能在一个类中。

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

We use a class that stores a map of thread id to data to implement our thread local storage. 我们使用一个类,它将线程id的映射存储到数据中,以实现我们的线程本地存储。 This seems to work very well, then an instance of this class can be placed anywhere you require thread local storage. 这似乎工作得很好,然后这个类的实例可以放在需要线程本地存储的任何地方。 Normally clients use an instance of as a static private field. 通常,客户端使用实例作为静态私有字段。

Here is a rough outline of the code 这是代码的大致轮廓

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;
};

This is then used as 然后将其用作

class A {
    // ...

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

ThreadLocal<Foo> A::threadLocalFoo;

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

This is simple and works on any platform where you can get the thread id. 这很简单,可以在任何可以获取线程ID的平台上运行。 Note the Critical Section is only used to return/create the reference, once you have the reference all calls are outside the critical section. 请注意,关键部分仅用于返回/创建引用,一旦您有引用,所有调用都在临界区之外。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM