为什么对于Dekker同步来说,C++ 11的acquire_release范围不够?

Jas*_*cek 15 multithreading synchronization atomic memory-fences c++11

Dekker式同步的失败通常通过重新排序指令来解释.即,如果我们写

atomic_int X;
atomic_int Y;
int r1, r2;
static void t1() { 
    X.store(1, std::memory_order_relaxed)
    r1 = Y.load(std::memory_order_relaxed);
}
static void t2() {
    Y.store(1, std::memory_order_relaxed)
    r2 = X.load(std::memory_order_relaxed);
}
Run Code Online (Sandbox Code Playgroud)

然后负载可以与商店重新排序,导致r1==r2==0.

我期待一个acquire_release围栏来防止这种重新排序:

static void t1() {
    X.store(1, std::memory_order_relaxed);
    atomic_thread_fence(std::memory_order_acq_rel);
    r1 = Y.load(std::memory_order_relaxed);
}
static void t2() {
    Y.store(1, std::memory_order_relaxed);
    atomic_thread_fence(std::memory_order_acq_rel);
    r2 = X.load(std::memory_order_relaxed);
}
Run Code Online (Sandbox Code Playgroud)

负载不能移动到栅栏上方,并且商店不能移动到栅栏下方,因此应该防止不良结果.

但是,实验表明r1==r2==0仍然可以发生.是否有基于重新排序的解释?我推理的缺陷在哪里?

Tob*_*ull 10

根据我的理解(主要来自阅读Jeff Preshings博客),a atomic_thread_fence(std::memory_order_acq_rel)可以防止任何重新排序,除了StoreLoad它,即它仍然允许重新排序Storea后续Load.但是,这正是您的示例中必须要防止的重新排序.

更确切地说,a atomic_thread_fence(std::memory_order_acquire)防止任何先前Load的任何后续Store和任何后续的重新排序Load,即,它防止LoadLoadLoadStore重新排序跨越围栏.

a atomic_thread_fence(std::memory_order_release)防止任何后续Store的任何前面Store和任何前面的重新排序Load,即,它防止LoadStoreStoreStore重新排序跨越围栏.

atomic_thread_fence(std::memory_order_acq_rel)则防止了工会,即,它可以防止LoadLoad,LoadStoreStoreStore,这意味着只有StoreLoad仍可能发生.


Ant*_*ton 5

memory_order_acq_rel实际上就像在同一个地方获取和释放栅栏一样。但问题在于,它们并不能阻止所有可能的重新排序,而是阻止后续加载或先前商店在围栏周围重新排序。因此,先前的负载和后续的存储仍然可以通过围栏。

在 Dekker 同步中,重要的是要防止例如加载在另一个线程中的存储之前被重新排序,即在栅栏之前。现在,在发生此同步的地方展开循环,您会发现前一次迭代的负载可以通过当前迭代的栅栏。

memory_order_seq_cst适用于 Dekker 的同步,因为它可以防止在这一点上进行任何重新排序。例如,使用它 Dekker 的算法和mfence窃取作品。

为了更好地理解,请参阅 Herb Sutter 讲座“原子<>武器 1/2 ”中的精彩动画,时间为 0:43。