19

现在 C++ 正在将thread_local存储添加为一种语言功能,我想知道一些事情:

  1. 可能的成本thead_local是多少?
    • 在记忆中?
    • 对于读写操作?
  2. 与此相关:操作系统通常如何实现这一点?似乎任何声明的东西thread_local都必须为创建的每个线程提供特定于线程的存储空间。
4

2 回答 2

14

存储空间:变量大小 * 线程数,或者可能是 (sizeof(var) + sizeof(var*)) * 线程数。

实现线程本地存储有两种基本方法:

  1. 使用某种系统调用来获取有关当前内核线程的信息。慢。

  2. 使用可能在处理器寄存器中的某个指针,该指针在内核每次线程上下文切换时正确设置 - 与所有其他寄存器同时。便宜的。

在英特尔平台上,变体 2 通常通过一些段寄存器(FS 或 GS​​,我不记得)实现。GCC 和 MSVC 都支持这一点。因此,访问时间与全局变量一样快。

这也是可能的,但我还没有在实践中看到它,因为这可以通过现有的库函数来实现,比如pthread_getspecific. 然后性能将像 1. 或 2.,加上库调用开销。请记住,变体 2. + 库调用开销仍然比内核调用快得多。

于 2011-12-13T13:24:12.123 回答
10

Uli Drepper(glibc 的维护者)对它如何在 Linux 上工作的描述可以在这里找到:www.akkadia.org/drepper/tls.pdf

处理动态加载模块等的要求使整个机制有点复杂,这也许部分解释了为什么文档的重量为 79 页(!)。

内存使用方面,每个线程变量显然需要它自己的每个线程内存(尽管在某些情况下,这可以延迟完成,以便仅在第一次访问变量时分配空间),然后有一些额外的数据结构偏移表等所需的。

在性能方面,访问 TLS 变量的额外成本主要围绕检索变量的地址。在 x86 Linux 上,GS 寄存器用作在 x86-64 FS 上获取线程 ID 的开始。通常有一些指针取消引用,以及用于动态加载代码的函数调用 (__tls_get_addr)。还存在创建新线程较慢的成本,因为实现需要分配空间并可能初始化所有 TLS 变量(如果不延迟完成)。

TLS 很适合轻松地使一些旧的线程不安全代码模式成为线程安全的(想想 errno),但对于从一开始就为多线程世界设计的新代码来说,它很少需要。

于 2011-12-13T14:23:17.130 回答