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

时间:2014-12-02 11:57:20

标签: multithreading c++11 synchronization atomic memory-fences

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);
}

然后可以使用商店对加载进行重新排序,从而导致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);
}

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

然而,实验表明r1==r2==0仍然可能发生。是否有基于重新排序的解释?我的推理中存在哪些缺陷?

2 个答案:

答案 0 :(得分:8)

根据我的理解(主要来自阅读Jeff Preshings blog),atomic_thread_fence(std::memory_order_acq_rel)会阻止除StoreLoad之外的任何重新排序,即它仍允许对Store重新排序随后的Load。但是,这正是您的示例中必须要防止的重新排序。

更准确地说,atomic_thread_fence(std::memory_order_acquire)会阻止任何后续Load与之后的任何Store以及任何后续Load的重新排序,即它会阻止LoadLoadLoadStore在篱笆上重新排序。

atomic_thread_fence(std::memory_order_release)会阻止任何后续Store与任何前面的Store和任何前面的Load进行重新排序,即它会阻止LoadStore和{{1跨越篱笆重新排序。

然后StoreStore会阻止联合,即它会阻止atomic_thread_fence(std::memory_order_acq_rel)LoadLoadLoadStore,这意味着只有StoreStore可能仍会发生。

答案 1 :(得分:4)

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

在Dekker同步中,重要的是要防止例如在另一个线程中存储之前,即在栅栏之前重新排序的负载。 现在,展开这种同步发生的循环,你会得到前一次迭代的负载可以通过当前迭代的栅栏落下。

memory_order_seq_cst适用于Dekker的同步,因为它可以阻止任何重新排序。例如,使用Dekker的算法和mfence来进行工作窃取。

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