11

我正在开发一个使用用户级上下文切换(使用 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因此编译器将 main 函数转换为以下几行(与汇编相反):

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 部分的线程读取tli属于线程 1 的值。

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

4

1 回答 1

7

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

使用与您描述的相同的技术来实现“继续窃取”。他们明确不鼓励在 Cilk 程序中使用 TLS。相反,他们建议使用“超对象”——Cilk 的一个特殊功能,它替代了 TLS(并且还提供了串行/确定性连接语义)。另请参阅有关并行性的Cilk 开发人员演示文稿。thread_local

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

于 2014-09-05T07:58:30.190 回答