16

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仍然可以发生。对此是否有基于重新排序的解释?我推理的缺陷在哪里?

4

2 回答 2

12

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

更准确地说, anatomic_thread_fence(std::memory_order_acquire)防止对任何先前Load的任何后续Store和任何后续的重新排序Load,即,它防止LoadLoadLoadStore重新排序跨越栅栏。

Anatomic_thread_fence(std::memory_order_release)防止任何后续Store与任何先前Store和任何先前的重新排序Load,即,它防止LoadStoreStoreStore重新排序跨越栅栏。

an atomic_thread_fence(std::memory_order_acq_rel)then 阻止并集,即它阻止LoadLoad, LoadStore, and StoreStore,这意味着 onlyStoreLoad可能仍然发生。

于 2014-12-03T10:52:31.800 回答
5

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

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

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

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

于 2014-12-02T15:50:14.937 回答