2014-12-02 89 views
14

Dekker式同步的失败通常是通过重新排序指令来解释的。即,如果我们写为什么C++ 11的acquire_release范围不够用于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仍然会发生。有没有基于重新排序的解释?我的推理在哪里存在缺陷?

回答

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,即,它可以防止在整个围栏LoadLoad和​​重排序。

一种atomic_thread_fence(std::memory_order_release)防止任何随后Store重排序与任一前述Store和任一前述Load,即,它可以防止在整个围栏​​和StoreStore重排序。

一种atomic_thread_fence(std::memory_order_acq_rel)然后阻止结合,即,它阻止LoadLoad,​​,和StoreStore,这意味着只有StoreLoad仍可能发生。

4

memory_order_acq_rel实际表现就如同在同一个地方获取和释放栅栏。但问题在于,它们不能阻止所有可能的重新排序,它们会阻止随后的负载或之前的商店在围栏周围重新排序。因此,之前的货物和随之而来的商店仍然可以穿过围栏。

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

memory_order_seq_cst对Dekker的同步工作正常,因为它阻止了这一点的任何重新排序。例如,使用Dekker算法和mfence进行工作窃取。

为了更好的理解,看到香草萨特讲座“Atomic<> weapons 1/2”伟大的动画,在0:43。

相关问题