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 在这里做啥来为每个线程运行一次此代码?的主要内容,如果未能解决你的问题,请参考以下文章

你做啥来保持用户登录反应本机应用程序

你在 SQL Server 中做啥来创建或更改?

我需要做啥来修复我的 Heroku 构建?

x-- 或 x++ 在这里做啥?

Groovy 在这里做啥?

Android线程内存泄漏