如何以原子方式和线程安全方式恰好一次初始化全局变量
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.
我已经创建了一个全局自旋锁变量和一个检查变量:
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.