我有一段旧的遗留代码:
if (current_Value > g_max_Value) g_max_Value=current_Value
正如您所了解的所有现代超级多线程,多CPU和大型CPU缓存一样,此代码无法正常工作。 问题:如何写得可靠,但优雅?
快速解决方案是将其包装在关键部分。但是,如果我理解正确,这不能保证CPU级别的原子。
答案 0 :(得分:2)
如果多个线程可能同时更新 json.put("ignore_volumes", true);
,则需要原子cmpxchg。
如果没有,那么你就不会,即使其他线程在一个线程写入时也能读取它。您可能仍需要确保存储和加载是原子的,但如果只有一个线程同时写入它,则不需要昂贵的原子读取 - 修改 - 写入。
如果您对更新对其他线程可见的顺序有任何要求,那么您还需要release / acquire memory ordering或类似的东西。如果没有,那么“宽松”的内存排序将确保操作是原子的,但不会在内存屏障上浪费指令或在编译时停止优化器重新排序。
ISO C11已将atomic compare-exchange作为语言的一部分。当然,它是一个交换 - 如果相等,因为这是硬件通常提供的,所以你需要一个循环来重试。
基本思路是对大于的进行比较,然后使用原子cmpxchg进行交换,因此仅在全局未更改时才会发生交换(因此比较结果仍然有效)。 如果自更新比较以来已更改,请重试。
g_max_Value
我们可以通过更改为#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()
这会编译成不同的代码,但我不确定它是否更好。
我将代码放在Godbolt compiler explorer上以查看它是否已编译并查看asm。不幸的是,Godbolt的ARM / ARM64 / PPC编译器太旧了(gcc 4.8),并且不支持C11 stdatomic,所以我只能看看x86 asm,我使用// 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;
}
代替{无关紧要{1}}(memory_order_relaxed
ed指令已经是完全内存屏障,正常负载是隐式获取负载。
我注意到这些包装器编译为更严格的代码
memory_order_seq_cst
因为他们不必返回值。