静态初始化的哪一部分是线程安全的?

What part of static initialization is thread safe?

提问人:cppguy 提问时间:11/16/2023 最后编辑:HolyBlackCatcppguy 更新时间:11/20/2023 访问量:63

问:

如果我有一个这样的全局值获取器

bool get_global_bool() {
    static const bool b{get_the_value()};
    return b;
}

我知道这将以线程安全的方式初始化,但我是否也能保证并发调用的多个线程永远不会执行多次?bget_global_boolget_the_value

C++ C++11 线程安全

评论

0赞 paddy 11/16/2023
是的,C++11 保证该值初始化一次。它将阻止其他线程同时命中此初始化代码,然后在初始化完成后取消阻止。此时,其他线程可以访问已初始化的值,绕过包含函数调用的整个初始化代码。
0赞 cppguy 11/16/2023
我想我的问题更多的是,是否有可能在任何同步发生之前发生对 get_the_value 的调用,并且唯一的线程安全位是在使用返回值“构造”布尔值时?
0赞 273K 11/16/2023
不,这是不可能的,整个表达式是线程安全的。

答:

1赞 kcbsbo 11/16/2023 #1

是的,它是根据 cppref

如果多个线程尝试初始化同一个静态本地 变量同时,初始化只发生一次(类似 可以使用 std::call_once) 获取任意函数的行为。

注意:此功能的通常实现使用双重检查锁定模式的变体,这减少了 已初始化为单个非原子布尔值的局部静态值 比较。

好吧,您也可以通过检查 gen asm 输出来验证它,输出类似于 x86-64 gcc trunk 上的以下内容

get_the_value():
        movzx   eax, BYTE PTR v[rip]
        ret
get_global_bool():
        movzx   eax, BYTE PTR guard variable for get_global_bool()::b[rip]
        test    al, al
        je      .L14
        movzx   eax, BYTE PTR get_global_bool()::b[rip]
        ret
.L14:
        sub     rsp, 8
        mov     edi, OFFSET FLAT:guard variable for get_global_bool()::b
        call    __cxa_guard_acquire
        test    eax, eax
        jne     .L15
        movzx   eax, BYTE PTR get_global_bool()::b[rip]
        add     rsp, 8
        ret
.L15:
        movzx   eax, BYTE PTR v[rip]
        mov     edi, OFFSET FLAT:guard variable for get_global_bool()::b
        mov     BYTE PTR get_global_bool()::b[rip], al
        call    __cxa_guard_release
        movzx   eax, BYTE PTR get_global_bool()::b[rip]
        add     rsp, 8
        ret

您可以看到get_the_value受 cxa_guard 保护,并且只调用一次

评论

0赞 James Kanze 11/21/2023
我很确定他们不会系统地使用双重检查锁定;它不适用于许多体系结构。在这种情况下,编译器可以看到它将工作,因为它已经内联了初始化函数,并且可以看到它没有对全局状态进行任何更改,但是根据初始化函数的内容,使用双重检查锁定,可能会出现特殊情况,即一个线程中的代码会看到初始化已经发生, 但会读取其他未发生的内存值。
0赞 James Kanze 11/21/2023
我在这里谈论的是反汇编代码中显示的低级双重检查锁定。可以使用 实现它,但要做到这一点,编译器必须生成一个围栏或一个 membar 或类似的东西。这在上面的汇编代码中不存在。atomic