我最近意识到线程本地存储在某些平台上是有限的。例如,C ++库boost :: thread read:
的文档“注意:可以创建的特定于线程的存储对象的数量存在特定于实现的限制,并且此限制可能很小。”
我一直在寻找尝试找出不同平台的限制,但我找不到权威表。如果您正在编写使用TLS的跨平台应用程序,这是一个重要问题。 Linux是我找到信息的唯一平台,以Ingo Monar补丁的形式于2002年发送到内核列表添加TLS支持,他提到“TLS区域的数量是无限的,没有 与TLS支持相关的额外分配开销。“如果在2009年仍然如此(是吗?)非常漂亮。
但是今天Linux怎么样? OS X?视窗?的Solaris?嵌入式操作系统?对于在多种体系结构上运行的操作系统,它是否因架构而异?
编辑:如果您对可能存在限制的原因感到好奇,请考虑预先分配线程本地存储的空间,因此您将为每个线程上的支付费用 。面对很多线程,即使是少量线程也是一个问题。
答案 0 :(得分:11)
在Linux上,如果您使用__thread
TLS数据,唯一的限制是由可用的地址空间设置的,因为这些数据只是被分配为gs
引用的常规RAM(在x86上)或fs
(在x86-64上)段描述符。请注意,在某些情况下,动态加载的库使用的TLS数据的分配可以在不使用该TLS数据的线程中省略。
pthread_key_create
和朋友分配的TLS仅限于PTHREAD_KEYS_MAX
个插槽(这适用于所有符合标准的pthreads实现)。
有关Linux上TLS实施的更多信息,请参阅ELF Handling For Thread-Local Storage和The Native POSIX Thread Library for Linux。
也就是说,如果你需要可移植性,最好的办法是尽量减少TLS的使用 - 在TLS中放一个指针,然后将你需要的所有东西都挂在指针上。
答案 1 :(得分:3)
我在Windows上只使用过TLS,版本之间存在细微差别,可以使用多少: http://msdn.microsoft.com/en-us/library/ms686749(VS.85).aspx
我认为您的代码仅针对支持线程的操作系统 - 过去我曾使用过不支持线程的嵌入式和桌面操作系统,所以不支持TLS。
答案 2 :(得分:1)
在Mac上,我知道Task-Specific Storage API中的Multiprocessing Services:
MPAllocateTaskStorageIndex
MPDeallocateTaskStorageIndex
MPGetTaskStorageValue
MPSetTaskStorageValue
这与Windows线程本地存储非常相似。
我不确定此API是否目前推荐用于Mac上的线程本地存储。也许有更新的东西。
答案 3 :(得分:0)
可能是提升文档只是谈论一般的可配置限制,而不一定是平台的一些硬限制。在Linux上,ulimit命令限制了资源进程可以拥有的数量(线程数,堆栈大小,内存和一堆其他东西)。这将间接影响您的线程本地存储。在我的系统上,似乎没有特定于线程本地存储的ulimit条目。其他平台可能有一种方法来指定它自己。此外,我认为在许多多处理器系统中,线程本地存储将位于专用于该CPU的内存中,因此您可能会在系统整体耗尽内存之前很久就遇到物理内存限制。我会假设在那种情况下有一些回退行为来定位主内存中的数据,但我不知道。你可以说,我猜想了很多。希望它仍然能引导你走向正确的方向......
答案 4 :(得分:0)
Windows上的线程本地存储declspec限制您仅将其用于静态变量,这意味着如果您想以更具创造性的方式使用它,则运气不佳。
Windows上有一个低级别的API,但它已经破坏了语义,这使得初始化非常难以理解:你无法判断你的线程是否已经看到该变量,因此你需要明确初始化当你创建线程时。
另一方面,用于线程本地存储的pthread API经过深思熟虑且灵活。
答案 5 :(得分: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()
。可以使用全局函数来调用它来推断模板参数。