访问静态初始化变量时应该使用屏障吗?

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_releaseacquire 作为确保静态初始化在原子 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.