现在 C++ 正在将thread_local
存储添加为一种语言功能,我想知道一些事情:
- 可能的成本
thead_local
是多少?- 在记忆中?
- 对于读写操作?
- 与此相关:操作系统通常如何实现这一点?似乎任何声明的东西
thread_local
都必须为创建的每个线程提供特定于线程的存储空间。
现在 C++ 正在将thread_local
存储添加为一种语言功能,我想知道一些事情:
thead_local
是多少?
thread_local
都必须为创建的每个线程提供特定于线程的存储空间。存储空间:变量大小 * 线程数,或者可能是 (sizeof(var) + sizeof(var*)) * 线程数。
实现线程本地存储有两种基本方法:
使用某种系统调用来获取有关当前内核线程的信息。慢。
使用可能在处理器寄存器中的某个指针,该指针在内核每次线程上下文切换时正确设置 - 与所有其他寄存器同时。便宜的。
在英特尔平台上,变体 2 通常通过一些段寄存器(FS 或 GS,我不记得)实现。GCC 和 MSVC 都支持这一点。因此,访问时间与全局变量一样快。
这也是可能的,但我还没有在实践中看到它,因为这可以通过现有的库函数来实现,比如pthread_getspecific
. 然后性能将像 1. 或 2.,加上库调用开销。请记住,变体 2. + 库调用开销仍然比内核调用快得多。
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),但对于从一开始就为多线程世界设计的新代码来说,它很少需要。