如果多个线程可能同时更新g_max_Value,您需要一个原子cmpxchg。
如果不是,那么你就不会,即使其他线程可能正在读取它,而一个线程正在写入它。您可能仍需要确保存储和加载是原子的,但如果只有一个线程同时写入,则不需要昂贵的原子读取-修改-写入。
如果您对更新对其他线程可见的顺序有任何要求,那么您还需要release / acquire memory ordering 或类似的东西。如果不是,那么“宽松”的内存排序将确保操作是原子的,但不会在内存屏障上浪费指令或在编译时停止优化器重新排序。
ISO C11 已经提供atomic compare-exchange 作为语言的一部分。当然,这是一个交换如果相等,因为这是硬件通常提供的,所以你需要一个循环来重试。
基本思想是对大于进行比较,然后使用原子 cmpxchg 进行交换,因此只有在全局未更改时才会进行交换(因此比较结果仍然有效)。 如果自比较后发生变化,请重试。
#include <stdatomic.h>
#include <stdbool.h>
atomic_int g_max_Value;
// if (current_Value > g_max_Value) g_max_Value=current_Value
bool update_gmaxval(int cur)
{
int tmpg = atomic_load_explicit(&g_max_Value, memory_order_relaxed);
if (cur <= tmpg)
return false;
// global value may change here but still be less than cur, so we need a loop insted of just a single cmpxchg_strong
while (!atomic_compare_exchange_weak_explicit(
&g_max_Value, &tmpg, cur,
memory_order_relaxed, memory_order_relaxed))
{
if (cur <= tmpg)
return false;
}
return true;
}
我们可以通过更改为do{}while() 循环来简化:
// if (current_Value > g_max_Value) g_max_Value=current_Value
bool update_gmaxval_v2(int cur)
{
int tmpg = atomic_load_explicit(&g_max_Value, memory_order_relaxed);
// global value may change here but still be less than cur, so we need a loop insted of just a single cmpxchg_strong
do {
if (cur <= tmpg)
return false;
} while (!atomic_compare_exchange_weak_explicit(
&g_max_Value, &tmpg, cur,
memory_order_relaxed, memory_order_relaxed));
return true;
}
这会编译成不同的代码,但我不确定它是否更好。
如果我们不返回真/假,我们会得到更高效的代码:
我把代码放在Godbolt compiler explorer 上看看它是否编译并查看asm。不幸的是,Godbolt 的 ARM/ARM64/PPC 编译器太旧(gcc 4.8),并且不支持 C11 stdatomic,所以我只能查看 x86 asm,我使用 memory_order_relaxed 而不是 memory_order_seq_cst 并不重要(locked 指令已经是完整的内存屏障,正常加载是隐式获取加载)。
我确实注意到这些包装器可以编译成更紧凑的代码
void update_gmaxval_void(int cur) { update_gmaxval(cur); }
void update_gmaxval_v2_void(int cur) { update_gmaxval_v2(cur); }
因为它们不必返回值。