如何实施“互锁比较交换”?

时间:2016-07-14 10:54:37

标签: c multithreading assembly multiprocessing

我有一段旧的遗留代码:

if (current_Value > g_max_Value) g_max_Value=current_Value

正如您所了解的所有现代超级多线程,多CPU和大型CPU缓存一样,此代码无法正常工作。 问题:如何写得可靠,但优雅?

快速解决方案是将其包装在关键部分。但是,如果我理解正确,这不能保证CPU级别的原子。

1 个答案:

答案 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()

这会编译成不同的代码,但我不确定它是否更好。

如果我们不返回true / false,我们会获得更高效的代码:

我将代码放在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

因为他们不必返回值。