如何以原子方式和线程安全方式恰好一次初始化全局变量

How to init a global variable exactly once, atomically and thread-safely

我已经创建了一个全局自旋锁变量和一个检查变量:

pthread_spinlock_t locking;
int check = 1;

现在我想精确初始化这个变量一次:

if (check == 1)
  {
    // atomic part begins here
    pthread_spin_init(&locking, PTHREAD_PROCESS_SHARED);
    check = 0;
    // atomic part ends here
  }

所以这两行应该以原子方式发生,我想让这件事线程保存。

有没有人知道如何处理这个问题?

我无法使用互斥锁锁定整个 if 语句。是否可以使用汇编指令使其成为原子?

所以当您可能正在修改 check 时,其他线程可以 运行 宁 if(check == 1)?没有办法通过 if 内部的锁来确保绝对安全,因为您在临界区 外部 具有读取访问权限。那将是数据竞争 UB。

如果 if(check==1) 部分应该一直 运行 并且几乎总是发现它是错误的,那么您希望该检查非常便宜并且可扩展到多个并行读取器。用锁保护该访问效率不高;所有读者都必须自己修改锁。

C11 引入了 <stdatomic.h>,使您可以方便地访问原子加载、存储和 RMW。你可以把 check 变成 atomic_int.

然后对它的只读访问可能与没有锁定的普通全局访问一样便宜。如果它被频繁读取而不被写入,它可以在每个核心的私有 L1d 缓存中保持热度。

#include <stdatomic.h>
#include <stdbool.h>

atomic_int check = 1;

void foo() {
    int old = 1;
    if (atomic_load_explicit(&check, memory_order_relaxed) == old) {
        bool success = atomic_compare_exchange_strong(&check, &old, 0);
        if (success) {
            // this thread did the exchange
        }
        // else some other thread saw check=1 and beat us to the punch
        // and old is updated to the previous value of check
    }
}

Godbolt 编译器资源管理器上编译为 check != 1 快速路径的高效 asm:

# gcc9.2 -O3 for x86-64
foo:
        mov     eax, DWORD PTR check[rip]     # plain asm load, atomic because it's aligned
        cmp     eax, 1
        je      .L4
        ret
.L4:
        xor     edx, edx
        lock cmpxchg    DWORD PTR check[rip], edx
        ret
check:
        .long   1

即使在像 AArch64 这样的弱排序 ISA 上,同样便宜的 asm。

atomic_int 的读取无法优化或提升到循环之外。

int tmp = check; 类似于 atomic_load_explicit,默认为 memory_order_seq_cst。在 x86 上,这在 asm 中不会花费任何额外费用,但在其他 ISA 上,它需要加载顺序障碍。我用了 relaxed;如果你想让它意味着可以安全地读取一些其他数据,你应该使用 acquire 或默认的 seq_cst.