由于每个线程都有自己的堆栈,因此可以将其私有数据放在上面。例如,每个线程可以分配一些堆内存来保存一些数据结构,并使用相同的接口来操作它。那么为什么特定于线程的数据是有帮助的呢?
我能想到的唯一情况是,每个线程可能有多种私有数据。如果我们需要访问该线程中调用的任何函数中的私有数据,我们需要将数据作为参数传递给所有这些函数,这既无聊又容易出错。
由于每个线程都有自己的堆栈,因此可以将其私有数据放在上面。例如,每个线程可以分配一些堆内存来保存一些数据结构,并使用相同的接口来操作它。那么为什么特定于线程的数据是有帮助的呢?
我能想到的唯一情况是,每个线程可能有多种私有数据。如果我们需要访问该线程中调用的任何函数中的私有数据,我们需要将数据作为参数传递给所有这些函数,这既无聊又容易出错。
线程局部存储是一种避免全局状态的解决方案。如果数据不是跨线程共享而是由多个函数访问,则可以使其成为线程本地的。无需担心破坏可重入性。使调试更容易。
从性能的角度来看,使用线程本地数据是一种避免错误共享的方法。假设您有两个线程,一个负责写入变量x,另一个负责读取变量y。如果要将它们定义为全局变量,它们可能位于同一缓存行上。这意味着如果其中一个线程写入x,CPU 将更新缓存行,这当然包括变量y,因此缓存性能会下降,因为没有理由更新y。
如果您使用线程本地数据,一个线程将只存储变量x而另一个只存储变量y,从而避免错误共享。但请记住,还有其他方法可以解决此问题,例如缓存行填充。
与堆栈不同(就像线程本地数据专用于每个线程),线程本地数据很有用,因为它通过函数调用持续存在(不像堆栈数据,如果在其函数之外使用可能已经被覆盖)。
另一种方法是使用专用于每个线程的相邻全局数据,但是当涉及 CPU 缓存时,这会影响性能。由于不同的线程很可能在不同的内核上运行,这种全局数据的“共享”可能会带来一些不希望的性能下降,因为来自一个内核的访问可能会使另一个内核的缓存行无效,而后者会导致更多的交互核心流量以确保缓存一致性。
相比之下,使用线程本地数据在概念上不应涉及弄乱其他内核的缓存。
将线程本地存储视为另一种全局变量。从某种意义上说,它是全球性的,您不必传递它,不同的代码可以随意访问它(当然,鉴于声明)。但是,每个不同的线程都有自己独立的变量。通常,全局变量在多线程编程中非常糟糕,因为其他线程可以更改该值。如果将其设为线程本地,则只有您的线程可以看到它,因此其他线程不可能意外更改它。
另一个用例是当您被迫使用(设计糟糕的)API 时,该 API 期望您使用全局变量将信息传递给回调函数。这是一个强制进入全局变量的简单实例,但使用线程本地存储使其线程安全。
好吧,我已经编写多线程应用程序 30 多年了,但从未发现任何需要使用 TLS。如果线程需要数据库绑定到线程的数据库连接,线程可以打开自己的一个并将其保留在堆栈上。由于线程不能被调用,只能发出信号,所以没有问题。每次我看到这个神奇的“TLS”时,我都意识到它不能解决我的问题。
在我典型的消息传递设计中,对象排队进入永不终止的线程,因此不需要 TLS。
使用线程池就更没用了。
我只能说使用 TLS=bad design。有人把我说对了,如果可以的话:)
我将线程本地存储用于数据库连接,有时用于请求/响应对象。举两个例子,都来自 Java webapp 环境,但原则是成立的。
Web 应用程序可能包含大量调用各种子系统的代码。其中许多可能需要访问数据库。就我而言,我编写了每个需要 db 从 db 池中获取 db 连接、使用该连接并将连接返回到池的子系统。线程本地存储提供了一种更简单的替代方案:创建请求时,从池中获取数据库连接并将其存储在线程本地存储中。然后每个子系统只使用来自线程本地存储的数据库连接,当请求完成时,它会将连接返回到数据库池。该解决方案具有性能优势,同时也不需要我通过每个级别传递数据库连接:即我的参数列表仍然更短。
在同一个 Web 应用程序中,我决定在一个远程子系统中真正想要查看 Web 请求对象。所以我必须重构以将这个对象一直向下传递,这将涉及大量的参数传递和重构,或者我可以简单地将对象放入线程本地存储中,并在需要时检索它。
在这两种情况下,您都可以争辩说我一开始就搞砸了设计,只是使用线程本地存储来保存我的培根。你可能有一点。但我也可以争辩说 Thread Local 使代码更简洁,同时保持线程安全。
当然,我必须非常确定我放入 Thread Local 的东西确实是每个线程一个且只有一个。对于 Web 应用程序,Request 对象或数据库连接非常适合此描述。
我想补充一下上面的答案,据我所知,在性能方面,堆栈分配比堆分配快。
关于跨调用传递本地数据,好吧 - 如果你在堆上分配,你需要将指针/引用(我是一个 Java 人:))传递给调用 - 否则,你将如何访问内存?
TLS 也可以很好地存储用于在线程内跨调用传递数据的上下文(我们使用它来保存跨线程登录用户的信息 - 某种会话管理)。
当特定线程的所有函数都需要访问一个公共变量时,使用线程特定数据。该变量是该特定线程的本地变量,但充当该线程所有函数的全局变量。
假设我们有任何进程的两个线程 t1 和 t2。变量“a”是 t1 的线程特定数据。然后,t2 不知道“a”,但 t1 的所有函数都可以将“a”作为全局变量访问。t1 的所有函数都会看到“a”的任何变化。
有了新的 OOP 技术,我发现线程特定的数据是无关紧要的。您可以传递函子,而不是将函数传递给线程。您传递的函子类可以保存您需要的任何线程特定数据。
例如。使用 C++11 或 boost 的示例代码如下所示
MyClassFunctor functorobj; <-- Functor Object. Can hold the function that runs as part of thread as well as any thread specific data
boost::thread mythread(functorobj);
Class MyClassFunctor
{
private:
std::list mylist; <-- Thread specific data
public:
void operator () ()
{
// This function is called when the thread runs
// This can access thread specific data mylist.
}
};