gcc 在这里做啥来为每个线程运行一次此代码?
Posted
技术标签:
【中文标题】gcc 在这里做啥来为每个线程运行一次此代码?【英文标题】:What is gcc doing here to run this code once per thread?gcc 在这里做什么来为每个线程运行一次此代码? 【发布时间】:2019-01-19 18:41:21 【问题描述】:我刚刚遇到了这种为每个线程运行一次代码的技术。我不知道它在最低级别是如何工作的。特别是,fs
指的是什么? .zero 8
是什么意思?标识符为@tpoff
是否有原因?
int foo();
void bar()
thread_local static auto _ = foo();
输出(带-O2):
bar():
cmp BYTE PTR fs:guard variable for bar()::_@tpoff, 0
je .L8
ret
.L8:
sub rsp, 8
call foo()
mov BYTE PTR fs:guard variable for bar()::_@tpoff, 1
add rsp, 8
ret
guard variable for bar()::_:
.zero 8
【问题讨论】:
似乎 GCC 正在使用每个线程变量来跟踪_
的状态。如果已经设置,它不会再次初始化它。
除了 Peter Cordes 所说的不使用 _
作为变量名之外,我还建议不要在示例中使用 main
,以显示编译器生成的汇编代码。函数main
在很多方面都很特别,虽然在这里它不是一个真正的问题,但编译器可以为 main 生成不同的代码,而不是它被命名为其他东西。特别是 main
在 C++ 中只能调用一次,因此编译器可以忽略 thread_local 属性或不使用保护变量。
哦,真的。既然你提到它,那绝对是个坏主意。
【参考方案1】:
fs
段基址是线程本地存储的地址(至少在 x86-64 Linux 上)。
.zero 8
保留 8 个字节的零(大概在 BSS 中)。查看 GAS 手册:https://sourceware.org/binutils/docs/as/Zero.html,链接在https://***.com/tags/x86/info。
@tpoff
大概意思是相对于线程本地存储来解决它,可能代表线程的东西偏移,我不知道。
它的其余部分看起来类似于 gcc 通常对需要运行时初始化程序的 static
局部变量所做的:每次进入函数时都会检查的保护变量,在已经初始化的情况下会失败。
1 字节的保护变量在线程本地存储中。实际的 _
本身已被优化掉,因为它从未被读取过。 请注意,foo
返回后,eax
没有存储。
顺便说一句,_
是变量名的奇怪(错误)选择。很容易错过它,并且可能保留给实现使用。
它在这里有一个很好的优化:通常(对于非线程本地static int var = foo();
)如果它发现保护变量尚未初始化,它需要一种线程安全的方式来确保只有一个线程实际执行初始化(本质上是锁定)。
但是这里每个线程都有自己的保护变量(并且应该第一次运行foo()
而不管其他线程在做什么)所以它不需要调用run_once
函数得到互斥。
(对不起,简短的回答,稍后我可能会在 https://godbolt.org/ 上使用非线程本地 static
局部变量的示例进行扩展。或者查找关于它的 SO Q&A。)
【讨论】:
对于一个除了每个线程运行一次初始化函数之外什么都不用的变量,你会使用什么变量名? @Artikash:dummy
之类的东西会起作用,或者用run_once_per_thread
之类的名称记录该用例的用途。我一直假设您的真实代码使用了它,而且这不在main
中。 C++ 标准表示从程序中调用main
是未定义的行为,因此实现可以将只需要在程序启动时运行一次的东西放在main
中。我认为以 main
作为入口点的新线程是 UB。
我的真实代码使用_
,虽然不是主要的。 std::terminate
的 MSVC 实现对每个线程都有一个单独的终止处理程序,因此我在每个线程中运行的函数中有 thread_local static auto _ = std::set_terminate(my_terminate);
。目前不确定是否要将其更改为thread_initializer_dummy
,我最初想保持未使用内容的javascript约定为_
。
@Artikash:我看不出为每个线程总是需要调用的东西使用静态本地的意义。您不能在每个线程只运行一次的函数开始附近正常调用它,这样就不需要保护了吗?例如您传递给std::thread
的函数。然后,如果您不需要结果,请不要将其分配给任何东西。
@Artikash:你必须以某种方式启动每个线程。他们在加入游泳池之前所做的事情不是在你的控制之下吗?这个函数只会在每个实际线程中运行一次,而不是每个分派到线程池的工作单元运行一次;我希望这就是你想要的。无论如何,开销很低,所以只要包含它的函数不在你最热门的热循环中,你就可以了。不过,它确实会触及额外的内存缓存行。以上是关于gcc 在这里做啥来为每个线程运行一次此代码?的主要内容,如果未能解决你的问题,请参考以下文章