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
仍然可以发生。对此有重新排序的解释吗?我推理中的缺陷在哪里?
发布于 2014-12-03 10:52:31
据我所知(主要是通过阅读博客),atomic_thread_fence(std::memory_order_acq_rel)
可以防止除StoreLoad
之外的任何重新排序,也就是说,它仍然允许重新排序带有后续Load
的Store
。然而,在您的示例中,这正是必须防止的重新排序。
更准确地说,atomic_thread_fence(std::memory_order_acquire)
防止将任何先前的Load
与任何后续的Store
和任何后续的Load
重新排序,也就是说,它防止了跨越栅栏的LoadLoad
和LoadStore
重新排序。
atomic_thread_fence(std::memory_order_release)
防止将任何后续的Store
与任何前面的Store
和任何前面的Load
重新排序,也就是说,它阻止了跨越栅栏的LoadStore
和StoreStore
重新排序。
然后,atomic_thread_fence(std::memory_order_acq_rel)
阻止联合,即阻止LoadLoad
、LoadStore
和StoreStore
,这意味着只有StoreLoad
仍然可能发生。
发布于 2014-12-02 15:50:14
实际上,memory_order_acq_rel
的行为就像在同一个地方获取和释放围栏一样。但问题是,他们并没有阻止所有可能的重新排序,他们阻止随后的货物或先前的商店被重新订购围绕围栏。因此,先前的负荷和随后的存储仍然可以通过栅栏。
在Dekker同步中,重要的是要防止负载在另一个线程中的存储前被重新排序,即在栅栏之前。现在,在发生同步的地方展开您的循环,您将得到来自前一个迭代的负载可以在当前迭代中通过栅栏。
memory_order_seq_cst
可以很好地实现Dekker的同步,因为它可以防止跨越这一点的任何重新排序。例如,tbb使用Dekker的算法和mfence
来窃取工作。
想要更好的理解,请参阅Herb讲座"Atomic<> weapons 1/2“中的伟大动画,在0:43。
https://stackoverflow.com/questions/27248856
复制相似问题