【问题标题】:Initialization of non-trivial thread_local variables: why on demand only?非平凡thread_local变量的初始化:为什么只按需?
【发布时间】:2020-08-29 01:24:23
【问题描述】:

thread_local 数据是 C++ 运行时使用来自.tdata/.tbss 部分的数据初始化的值(假设 ELF abi)。因此,添加另一对称为.tinit/.tfini 的部分似乎是可行的。这些部分可能包含在从属线程启动和终止以及线程本地内存建立后始终运行的代码(类似于在主线程开始/结束时执行的.init/.fini 部分)。非平凡线程本地对象的构造函数和析构函数然后可以被链接器集中到这些对象中。

假定的.tfini 部分的功能目前由__cxa_thread_atexit 和朋友的动态机制处理(开销很大)。但是,运行时没有为“自动”线程初始化代码提供专门的工具:对于所有非平凡的线程本地对象,编译器必须发出在每次访问时都会检查的保护变量。

所以问题是:线程本地数据功能架构师是否评估了上述方法?发现了哪些缺点(除了显然不是一成不变的线程执行语义的更改)?

【问题讨论】:

  • 只是好奇,这个答案有用吗?
  • 不,不是。
  • 我建议你问清楚问题。

标签: c++11 elf thread-local-storage


【解决方案1】:

正如您所指出的,ELF 线程局部变量(即标记为__thread)必须使用常量表达式进行初始化。这是由于一些关于实施的设计决定。

洞察 1:大多数线程不会访问应用程序中可用的所有线程本地数据。想象一些计算库启动多个线程以加速执行 - 这些线程很可能访问该库的内部线程本地,而不是来自其他库的线程本地。这就是 Glibc 按需分配线程本地存储的原因,即当 app.代码首先通过__tls_get_addr 访问它(一些 TLS 块将在启动时分配,但这更像是一种优化而不是要求)。

见解 2:允许任意构造函数在 TLS 分配期间工作(即在 __tls_get_addr 内部)将是一个巨大死锁竞争的来源,因为可能会调用 __tls_get_addr在代码中的任意点(每当第一次引用 TLS 变量时)。

我相信这就是导致设计者一起禁止动态初始化 TLS 变量的原因。

【讨论】:

  • 你有“一起禁止动态初始化”的参考吗?几年前,C++ 委员会讨论了动态线程初始化部分。我有兴趣看到这些讨论结果的链接。此外,当涉及到现实世界的应用程序时,您的“见解 1”大多是不正确的(考虑日志记录和指标库、随机状态/加密提供程序和花哨的并发数据结构)。
  • @oakad 其实我以为我们只是在讨论 GCC TLS。 their docs 中提到了常量初始化器要求。请注意,GNU TLS 比 C++ TLS 早了约 7 年。
猜你喜欢
  • 2017-09-11
  • 2014-08-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多