线程局部单例

发布于 2024-07-30 00:44:09 字数 1001 浏览 11 评论 0原文

我想创建一个单例类,在使用它的每个线程中实例化一次。 我想将实例指针存储在 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. 是否可以注册一个回调,允许我在特定线程结束时清理与该线程关联的实例?

    是否可以

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. 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. 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. 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.

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?

  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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

長街聽風 2024-08-06 00:44:12

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

We use a class that stores a map of thread id to data to implement our thread local storage. 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. Note the Critical Section is only used to return/create the reference, once you have the reference all calls are outside the critical section.

隐诗 2024-08-06 00:44:11

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

Have a look at this paper to understand why double-checked locking doesn't work in general (even though it might work in special cases).

沉默的熊 2024-08-06 00:44:11

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

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

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

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

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? 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. 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. 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. (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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文