【问题标题】:Making thread_local variables fully volatile使 thread_local 变量完全易失
【发布时间】:2014-09-04 19:43:34
【问题描述】:

我正在开发一个使用用户级上下文切换(使用 Boost::Context)的运行时库,但在使用 thread_level 变量时遇到了问题。考虑以下(简化的)代码:

thread_local int* volatile tli;

int main()
{
    tli = new int(1);   // part 1, done by thread 1
    UserLevelContextSwitch();
    int li = *tli;      // part 2, done by thread 2
    cout << li;
}

由于对thread_local 变量的访问有两次,因此编译器将主函数转换为以下内容(从汇编反转):

register int** ptli = &tli; // cache address of thread_local variable
*ptli = new int(1);
UserLevelContextSwitch();
int li = **ptli;
cout << li;

这似乎是一个合法的优化,因为 volatile tli 没有被缓存在寄存器中。但是地址 volatile tli 实际上被缓存了,而不是在第 2 部分从内存中读取。

这就是问题所在:在用户级上下文切换之后,执行第 1 部分的线程转到了其他地方。第 2 部分然后被其他线程拾取,该线程获取先前的堆栈并注册状态。但是现在正在执行第 2 部分的线程读取属于线程 1 的 tli 的值。

我试图找出一种方法来防止编译器缓存线程局部变量的地址,而volatile 的深度不够。是否有任何技巧(最好是标准的,可能是 GCC 特定的)来防止缓存线程局部变量的地址?

【问题讨论】:

  • 我这里可能很密集,但是thread_local你自己做线程有什么好处呢?
  • @Mgetz,线程局部变量不是本质上是原子的吗?...如果编译器对原子过于保守,您的建议可能会起作用,但它不应该真的有缓存问题原子变量的地址与非原子变量一样。
  • C++11 的线程模型假定执行线程将从函数的顶部开始,然后遵循正常的调用顺序——它不支持在函数中间“开始”的线程函数(C++11 1.10/1“多线程执行和数据竞争”)。这并不是说您将无法解决您的问题,但我怀疑是否会有标准的方法来解决问题。不过,这个问题很好。
  • @500-InternalServerError,我的库管理任务,可以从 OS 线程迁移到 OS 线程。我需要一种方法来为每个正在运行的任务提供一些自己的身份信息。当任务映射到线程时,我管理身份的设置,但由于这个问题,身份的东西可能被映射到错误的线程。将身份信息作为每个调用的参数移动是不可取的,因为这会影响用户代码。
  • @eran:我理解你的问题,但是线程本地的实际 value 不会跟随硬件线程(因为没有更好的术语)?跨度>

标签: c++ multithreading volatile thread-local boost-context


【解决方案1】:

无法将用户级上下文切换与 TLS 配对。即使使用原子和完整的内存栅栏,缓存地址似乎也是合理的优化,因为 thread_local 变量是文件范围的静态变量,编译器假定它不能移动。 (不过,也许一些编译器仍然对编译器内存屏障很敏感,例如 std::atomic_thread_fenceasm volatile ("" : : : "memory");

使用the same technique 如您所述实现“继续窃取”,当不同的线程可以在同步点之后继续执行时。他们explicitly discourage 在 Cilk 程序中使用 TLS。相反,他们建议使用“超对象”——Cilk 的一个特殊功能,它替代了 TLS(并且还提供了串行/确定性连接语义)。另请参阅 Cilk 开发人员 presentation 关于 thread_local 和并行性。

此外,当Fibers(相同的轻量级上下文切换)正在使用时,Windows 提供 FLS(光纤本地存储)作为 TLS 替代品。

【讨论】:

  • 有趣。我深入研究了 Cilk 运行时代码,发现他们确实使用 TLS internally 来获取当前任务的描述符。但是 TLS 的使用通过诸如超对象(实际上是一个库)之类的抽象与用户代码分开。我的项目还缺乏编译器支持,因此用户代码中混杂着应该由编译器生成的代码。这可能会影响性能,但我会尝试将 TLS 命令与实际使用分开。
  • 如果您依赖特定的编译器 (gcc),我会查看 TLS ABI 并尝试强制编译器重新计算地址(将 GS 放入破坏列表?手动替换地址?)。
  • 我希望 TLS 有一些我可以使用的低级 API(内在),但找不到任何,并且生成的程序集似乎不容易复制(太多“硬编码”偏移量)。我将从使用 TL 变量的访问器开始,并将它们定义为 noinline。希望这能防止编译器忽略调用。
  • 有 API 但用于慢速路径。请参阅 TLS ABI here
猜你喜欢
  • 2018-12-06
  • 2014-08-06
  • 1970-01-01
  • 2017-09-11
  • 2023-02-14
  • 1970-01-01
  • 1970-01-01
  • 2014-05-08
  • 2010-09-25
相关资源
最近更新 更多