访问静态初始化变量时应该使用屏障吗?
Should I use a barrier while accessing statically initialized variable?
在我的函数中有以下两行代码:
static volatile uint64_t static_index = 0;
const uint64_t index = __sync_fetch_and_add(&static_index, 1, __ATOMIC_RELAXED);
如您所见,static_index
在线程之间共享,而 index
是每个线程共享的。
我担心的是,使用此变量可能会重新排序静态初始化,但我不确定这是否可以应用于静态(一次)初始化的变量。
在这种情况下 __ATOMIC_RELAXED 是否足以避免重新排序?或者我应该在这里使用 __ATOMIC_RELEASE
甚至 __ATOMIC_SEQ_CST
?
感谢任何帮助,谢谢。
您的静态初始值设定项是一个编译时常量,因此您可以(至少在实践中)指望静态存储 space 在您的进程启动时已经持有 0
。
(具体来说,它将在此处的 BSS 中。非零常量将意味着它进入 .data
部分。)
我很确定它对于非常量初始化程序也是安全的。
对于具有非常量初始值设定项的函数作用域静态变量,第一个进入函数的线程运行初始值设定项。编译器通常使用保护变量。快速情况(已初始化)涉及对该保护变量的获取加载以检查静态变量是否已被初始化。否则,原子 RMW 确保只有 1 个线程运行初始化程序,而其他线程等待它。
但抛开实现细节不谈:我没有仔细检查标准对静态变量的描述。但是在执行初始化的线程中,static volatile foo = x
显然在其上的RMW之前排序,因此可以保证在之前发生。
在其他线程中,它们是否可以使用静态初始化重新排序的问题。我认为答案一定是否定的,否则你会在没有原子内置函数的情况下读取或写入数据争用 UB。
在一个线程中,您可以查看 static foo = non_const;
以确保 foo
已初始化。即使我们不是执行初始化的线程。
如果其他线程正在与我们竞争,memory_order_release
或 acquire
作为确保静态初始化在原子 RMW 之前完成的方法没有意义。从其他线程的 POV 控制 our 操作的可见性顺序。 我很确定语言规则只要求 RMW 在 static foo = bar
暗示的所有事情之后发生(无论是执行初始化还是在必要时等待它)因为顺序排序。 如果考虑非原子情况,其他任何事情都没有任何意义。您不能让其他线程读取未初始化的变量。
(请注意,C 仅支持函数作用域静态变量的非常量初始化器。只有 C++ 支持全局变量。)
顺便说一句,没有理由使用 deprecated/legacy GNU C __sync
builtins: The manual says: They should not be used for new code which should use the ‘__atomic’ builtins instead.
__sync
内置函数的第三个参数不是内存顺序,它是 GCC 忽略的“受内存屏障保护的可选变量列表”。它是 __atomic_fetch_add
接受内存顺序参数。
或者在大多数情况下更好,C11 <stdatomic.h>
for _Atomic static uint64_t static_index = 0;
并修改为 https://en.cppreference.com/w/c/atomic/atomic_fetch_add
atomic_fetch_add_explicit(&static_index, 1, memory_order_relaxed);
(或者如果你愿意,idx = static_index++;
但默认为 seq_cst 因此对于非 x86 ISA 的编译效率较低。)
您不需要 volatile _Atomic
,因此您可以删除 volatile
类型限定符。现在 C11 / C++11 可用,将 volatile
用于手动松散原子是 generally not recommended,但如果你这样做,那么直接 load/store 访问 volatile
有点像像 _Atomic 和 mo_relaxed.
在我的函数中有以下两行代码:
static volatile uint64_t static_index = 0;
const uint64_t index = __sync_fetch_and_add(&static_index, 1, __ATOMIC_RELAXED);
如您所见,static_index
在线程之间共享,而 index
是每个线程共享的。
我担心的是,使用此变量可能会重新排序静态初始化,但我不确定这是否可以应用于静态(一次)初始化的变量。
在这种情况下 __ATOMIC_RELAXED 是否足以避免重新排序?或者我应该在这里使用 __ATOMIC_RELEASE
甚至 __ATOMIC_SEQ_CST
?
感谢任何帮助,谢谢。
您的静态初始值设定项是一个编译时常量,因此您可以(至少在实践中)指望静态存储 space 在您的进程启动时已经持有 0
。
(具体来说,它将在此处的 BSS 中。非零常量将意味着它进入 .data
部分。)
我很确定它对于非常量初始化程序也是安全的。
对于具有非常量初始值设定项的函数作用域静态变量,第一个进入函数的线程运行初始值设定项。编译器通常使用保护变量。快速情况(已初始化)涉及对该保护变量的获取加载以检查静态变量是否已被初始化。否则,原子 RMW 确保只有 1 个线程运行初始化程序,而其他线程等待它。
但抛开实现细节不谈:我没有仔细检查标准对静态变量的描述。但是在执行初始化的线程中,static volatile foo = x
显然在其上的RMW之前排序,因此可以保证在之前发生。
在其他线程中,它们是否可以使用静态初始化重新排序的问题。我认为答案一定是否定的,否则你会在没有原子内置函数的情况下读取或写入数据争用 UB。
在一个线程中,您可以查看 static foo = non_const;
以确保 foo
已初始化。即使我们不是执行初始化的线程。
memory_order_release
或 acquire
作为确保静态初始化在原子 RMW 之前完成的方法没有意义。从其他线程的 POV 控制 our 操作的可见性顺序。 我很确定语言规则只要求 RMW 在 static foo = bar
暗示的所有事情之后发生(无论是执行初始化还是在必要时等待它)因为顺序排序。 如果考虑非原子情况,其他任何事情都没有任何意义。您不能让其他线程读取未初始化的变量。
(请注意,C 仅支持函数作用域静态变量的非常量初始化器。只有 C++ 支持全局变量。)
顺便说一句,没有理由使用 deprecated/legacy GNU C __sync
builtins: The manual says: They should not be used for new code which should use the ‘__atomic’ builtins instead.
__sync
内置函数的第三个参数不是内存顺序,它是 GCC 忽略的“受内存屏障保护的可选变量列表”。它是 __atomic_fetch_add
接受内存顺序参数。
或者在大多数情况下更好,C11 <stdatomic.h>
for _Atomic static uint64_t static_index = 0;
并修改为 https://en.cppreference.com/w/c/atomic/atomic_fetch_add
atomic_fetch_add_explicit(&static_index, 1, memory_order_relaxed);
(或者如果你愿意,idx = static_index++;
但默认为 seq_cst 因此对于非 x86 ISA 的编译效率较低。)
您不需要 volatile _Atomic
,因此您可以删除 volatile
类型限定符。现在 C11 / C++11 可用,将 volatile
用于手动松散原子是 generally not recommended,但如果你这样做,那么直接 load/store 访问 volatile
有点像像 _Atomic 和 mo_relaxed.