Hol*_*Cat 6 c++ concurrency atomic language-lawyer stdatomic
我试图了解std::atomic_thread_fence(std::memory_order_seq_cst);栅栏的用途,以及它们与栅栏有何不同acq_rel。
到目前为止,我的理解是,唯一的区别是 seq-cst 栅栏影响 seq-cst 操作的全局顺序 ( [atomics.order]/4)。并且只有在实际执行 seq-cst 加载时才能观察到所述顺序。
所以我想,如果我没有 seq-cst 负载,那么我可以用 acq-rel 栅栏替换所有 seq-cst 栅栏,而不改变行为。那是对的吗?
如果这是正确的,为什么我会看到这样的代码“使用栅栏实现 Dekker 算法”,它使用 seq-cst 栅栏,同时保持所有原子读/写宽松?这是该博客文章中的代码:
std::atomic<bool> flag0(false),flag1(false);
std::atomic<int> turn(0);
void p0()
{
flag0.store(true,std::memory_order_relaxed);
std::atomic_thread_fence(std::memory_order_seq_cst);
while (flag1.load(std::memory_order_relaxed))
{
if (turn.load(std::memory_order_relaxed) != 0)
{
flag0.store(false,std::memory_order_relaxed);
while (turn.load(std::memory_order_relaxed) != 0)
{
}
flag0.store(true,std::memory_order_relaxed);
std::atomic_thread_fence(std::memory_order_seq_cst);
}
}
std::atomic_thread_fence(std::memory_order_acquire);
// critical section
turn.store(1,std::memory_order_relaxed);
std::atomic_thread_fence(std::memory_order_release);
flag0.store(false,std::memory_order_relaxed);
}
void p1()
{
flag1.store(true,std::memory_order_relaxed);
std::atomic_thread_fence(std::memory_order_seq_cst);
while (flag0.load(std::memory_order_relaxed))
{
if (turn.load(std::memory_order_relaxed) != 1)
{
flag1.store(false,std::memory_order_relaxed);
while (turn.load(std::memory_order_relaxed) != 1)
{
}
flag1.store(true,std::memory_order_relaxed);
std::atomic_thread_fence(std::memory_order_seq_cst);
}
}
std::atomic_thread_fence(std::memory_order_acquire);
// critical section
turn.store(0,std::memory_order_relaxed);
std::atomic_thread_fence(std::memory_order_release);
flag1.store(false,std::memory_order_relaxed);
}
Run Code Online (Sandbox Code Playgroud)
据我了解,它们并不相同,下面是一个反例。我相信你的逻辑错误在这里:
并且只有在实际执行 seq-cst 加载时才能观察到所述顺序。
我不认为这是真的。在定义顺序一致性总顺序 S 的公理的atomics.order p4 中,第 2-4 项都可能涉及不是 的操作seq_cst。您可以观察这些操作之间的连贯性顺序,这可以让您推断seq_cst操作的顺序。
作为示例,请考虑以下版本的 StoreLoad 石蕊测试,类似于 Peterson 算法:
std::atomic<bool> a,b; // initialized to false
void thr1() {
a.store(true, std::memory_order_seq_cst);
std::atomic_thread_fence(std::memory_order_seq_cst);
if (b.load(std::memory_order_relaxed) == false)
std::cout << "thr1 wins";
}
void thr2() {
b.store(true, std::memory_order_seq_cst);
std::atomic_thread_fence(std::memory_order_seq_cst);
if (a.load(std::memory_order_relaxed) == false)
std::cout << "thr2 wins";
}
Run Code Online (Sandbox Code Playgroud)
注意所有负载都是relaxed。
我声称,如果 thr1 打印“thr1 win”,那么我们就可以推断出它
在顺序一致性顺序 S 中a.store(true)位于前面。b.store(true)
为了看到这一点,设 A 为b.load(),B 为b.store(true)。如果
b.load() == false我们有 A 在 B 之前是一致有序的。(应用atomics.order p3.3 与上面的 A,B,X 的初始化b。false)
现在让 X 为 thr1 中的栅栏。然后,通过排序,X 发生在 A 之前,因此根据atomics.order p4.3,X 在 S 中先于 B。也就是说, thr1 栅栏位于 之前b.store(true)。并且a.store(true),也是seq_cst,位于 thr1 栅栏之前,因为通过排序,存储强烈发生在栅栏之前。因此,根据传递性,如所声称的那样,a.store(true)先于b.store(true)。
同样,如果 thr2 打印,则 thenb.store(true)位于 之前
a.store(true)。它们不能同时出现在彼此之前,因此我们证明了程序无法打印这两条消息。
如果将栅栏降级为acq_rel,证明就会失效。在这种情况下,据我所知,thr1 wins即使以S 顺序b.store(true)执行,也没有什么可以阻止程序打印a.store(true)
。因此,使用acq_rel栅栏,我相信两个线程都可以打印。尽管我不确定是否有任何真正的实施可以真正发生。
如果我们进行所有的加载和存储 relaxed,我们可以得到一个更简单的例子,这样唯一的seq_cst操作就是栅栏。然后我们可以使用(4.4)来代替,以表明如果b.load(relaxed)返回,则 thr1 栅栏先于 thr2 栅栏,如果返回 ,false则反之亦然。因此,像以前一样,我们得出结论,程序无法打印这两条消息。a.load()false
然而,如果我们保持负载和存储放松并将围栏削弱到acq_rel,那么更明显的是我们已经失去了这种保证。事实上,经过一点点刺激,类似的示例实际上在 x86 上失败了,其中acq_rel栅栏是无操作的,因为加载/存储已经获取/释放。所以这是一个更清楚的例子,seq_cst栅栏确实实现了一些acq_rel不能实现的目标。