2009-09-22 63 views
13

我最近意识到线程本地存储在某些平台上是有限的。例如,C++库boost :: thread读取的文档:在哪些平台上线程本地存储空间有限并有多少可用空间?

“注意:对于可以创建的线程特定存储对象的数量存在实现特定的限制,并且此限制可能很小。”

我一直在寻找尝试找出不同平台的限制,但我一直没有找到权威的表。如果您正在编写使用TLS的跨平台应用程序,这是一个重要问题。 Linux是我找到信息的唯一平台,采用Ingo Monar在2002年发布的内核列表形式,添加了TLS支持,他提到:“TLS区域的数量是无限的,并且没有额外的分配开销与TLS支持相关联。“如果2009年仍然如此(是吗?)是非常漂亮的。

但今天的Linux呢? OS X?视窗?的Solaris?嵌入式操作系统?对于在多种体系结构上运行的操作系统,它在不同体系结构中有所不同?

编辑:如果您好奇为什么可能有限制,请考虑线程本地存储的空间将被预分配,因此您将在每个线程上支付的费用。面对很多线程,即使只有少量也是一个问题。

回答

0

这可能是boost文档只是谈论一个通用的可配置限制,而不一定是平台的一些硬性限制。在Linux上,ulimit命令可以限制资源进程(线程数,堆栈大小,内存以及其他一些东西)。这将间接影响你的线程本地存储。在我的系统中,似乎没有特定于线程本地存储的ulimit条目。其他平台可能有自己的方式来指定。另外,我认为在许多多处理器系统中,线程本地存储将存储在专用于该CPU的内存中,因此,在系统整体存储器耗尽之前,您可能会遇到物理内存限制。在这种情况下,我会假设有一种回退行为来查找主存中的数据,但我不知道。你可以说,我猜测很多。希望它仍然会导致你在正确的方向...

9

在Linux上,如果使用的是__thread TLS数据,唯一的限制是由您提供的地址空间设置,因为这个数据仅仅分配由gs引用(在x86)定期RAM或fs(上X86-64 )段描述符。请注意,在某些情况下,动态加载库的TLS数据的分配可以在不使用该TLS数据的线程中消除。

但是,由pthread_key_create和朋友分配的TLS限于PTHREAD_KEYS_MAX插槽(这适用于所有符合pthreads实现)。

有关Linux上TLS实现的更多信息,请参见ELF Handling For Thread-Local StorageThe Native POSIX Thread Library for Linux。也就是说,如果你需要可移植性,最好的办法就是尽量减少TLS的使用 - 在TLS中放一个指针,并把你需要的所有东西放在数据结构中。

0

Windows上的线程本地存储器declspec限制了您仅将其用于静态变量,这意味着如果您想以更有创意的方式使用它,那么您运气不佳。

在Windows上有一个低级别的API,但它已经破坏了语义,这使得初始化非常尴尬:您无法确定变量是否已经被您的线程看到,因此您需要明确在创建线程时初始化它。

另一方面,用于线程本地存储的pthread API经过深思熟虑并且非常灵活。

0

我使用一个简单的模板类来提供线程本地存储。这只是包装std::map和一个关键部分。然后,这不会受到任何平台特定的线程本地问题,唯一的平台要求是以整数形式获取当前线程ID。它可能比本地线程本地存储慢一点,但它可以存储任何数据类型。

下面是我的代码的减少版本。我已经删除了默认值逻辑来简化代码。由于它可以存储任何数据类型,因此只有在T支持它们时才能使用增量和减量运算符。关键部分仅用于保护查找并插入到地图中。一旦返回引用,使用不受保护的是安全的,因为只有当前线程将使用此值。

template <class T> 
class ThreadLocal 
{ 
public: 
    operator T() 
    { 
     return value(); 
    } 

    T & operator++() 
    { 
     return ++value(); 
    } 

    T operator++(int) 
    { 
     return value()++; 
    } 

    T & operator--() 
    { 
     return --value(); 
    } 

    T operator--(int) 
    { 
     return value()--; 
    } 

    T & operator=(const T& v) 
    { 
     return (value() = v); 
    } 

private: 
    T & value() 
    { 
     LockGuard<CriticalSection> lock(m_cs); 
     return m_threadMap[Thread::getThreadID()]; 
    } 

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

要使用这个类,我一般声明静态成员的类如中

class DBConnection { 
    DBConnection() { 
     ++m_connectionCount; 
    } 

    ~DBConnection() { 
     --m_connectionCount; 
    } 

    // ... 
    static ThreadLocal<unsigned int> m_connectionCount; 
}; 

ThreadLocal<unsigned int> DBConnection::m_connectionCount 

它可能不是完美的每一种情况,但它涵盖了我的需要,我可以轻松地添加它是任何功能当我发现它们时错过了。

bdonlan是否正确本示例在线程退出后不会清理。但是,这很容易添加手动清理。

template <class T> 
class ThreadLocal 
{ 
public: 
    static void cleanup(ThreadLocal<T> & tl) 
    { 
     LockGuard<CriticalSection> lock(m_cs); 
     tl.m_threadMap.erase(Thread::getThreadID()); 
    } 

    class AutoCleanup { 
    public: 
     AutoCleanup(ThreadLocal<T> & tl) : m_tl(tl) {} 
     ~AutoCleanup() { 
      cleanup(m_tl); 
     } 

    private: 
     ThreadLocal<T> m_tl 
    } 

    // ... 
} 

然后,知道它使得明确使用ThreadLocal都可以使用ThreadLocal::AutoCleanup在其主要功能来清理变量的线程。

或者在DBConnection的

~DBConnection() { 
    if (--m_connectionCount == 0) 
     ThreadLocal<int>::cleanup(m_connectionCount); 
} 

cleanup()方法是静态的,以便不与operator T()干扰的情况下。全局函数可以用来调用这个可以推断出模板参数的函数。

+0

线程死后不能清理... – bdonlan 2009-09-22 20:01:08

+0

你是对的,目前清理工作必须手动完成 – iain 2009-09-23 07:45:33

1

在Mac上,我的Multiprocessing Services API中知道的Task-Specific Storage

MPAllocateTaskStorageIndex 
MPDeallocateTaskStorageIndex 
MPGetTaskStorageValue 
MPSetTaskStorageValue 

这看起来非常类似于Windows线程本地存储。

我不确定当前是否推荐将此API用于Mac上的线程本地存储。也许有更新的东西。

相关问题