通过使用原子操作作为更高级别同步设施的基础来排序非原子操作

问题描述 投票:0回答:3

我首先引用Anthony Williams的“C ++并发行动”中的一些描述:

class spinlock_mutex

{

 std::atomic_flag flag;

public:

 spinlock_mutex():

     flag(ATOMIC_FLAG_INIT)
 {}

 void lock()

 {

     while(flag.test_and_set(std::memory_order_acquire));
 }

 void unlock()

 {

     flag.clear(std::memory_order_release);
 }

};

lock()操作是使用std :: memory_ order_acquire排序的flag.test_and_set()循环,而unlock()是使用std :: memory_order_release排序调用flag.clear()。当第一个线程调用lock()时,该标志最初是清除的,因此第一次调用test_and_set()将设置该标志并返回false,表示该线程现在具有锁定,并终止循环。然后,该线程可以自由修改受互斥锁保护的任何数据。此时调用lock()的任何其他线程将找到已设置的标志,并将在test_and_set()循环中被阻止。

当带锁的线程完成修改受保护的数据时,它会调用unlock(),它使用std :: memory_order_release语义调用flag.clear()。然后,这将与另一个线程上的lock()调用一起调用(参见5.3.1节)后续调用flag.test_and_set(),因为这个调用具有std :: memory_order_acquire语义。因为在unlock()调用之前必须对受保护数据的修改进行排序,所以此修改在unlock()之前发生,因此在第二个线程的后续lock()调用之前发生(因为解锁之间的同步关系) ()和lock())并且在它获得锁之后从第二个线程访问该数据之前发生。

问:如果只有两个线程,并且线程A有对象m1第一次调用lock(),而线程B有对象m1lock()在线程A中调用m1之前第一次调用unlock(),为什么flag.test_and_set(std::memory_order_acquire)变为true而不是当m1在线程B中调用lock函数时,是否为false(初始值)?

我知道发布序列,但构成一个发布序列需要一个原子对象调用std::memory_order_release的原子操作,并且没有用std::memory_order_release调用的操作。

multithreading c++11 atomic memory-barriers
3个回答
1
投票

acquirerelease语义与其他(受保护)资源相关,此处未显示。特别是,在锁定之后或解锁之前不要移动访问。原子操作本身是完全有序的。

由于操作是完全有序的,因此两个线程都会以相同的顺序看到您的假设订单A:lock, B:lock, A:unlock。因此,当线程B调用lock时,它只能看到来自A的lock而不是unlock


1
投票

除了具有你想知道的行为之外,线程在彼此之前做事并没有真正意义。 memory_order没有进入这个。它指定了如何围绕原子操作对常规非原子内存访问进行排序。

拥有它的原因是,如果你这样做:

lock();
foo();
unlock();

在两个线程中,一个线程中的foo在锁定之前或解锁之后无法读取或解析相关线程。这与锁定和解锁自身的原子性相结合,给出了我们期望的行为。 (即没有来自foo的并发访问)。


0
投票

只有一个std::atomic_flag。在任何时候,它可以设置(true)或清除(false)。

std::atomic_flag::test_and_set定义为

以原子方式将std::atomic_flag的状态更改为set(true)并返回之前保存的值。

当A调用lock时,它已将标志更改为set,因此设置了B尝试锁定时返回的状态。这被评估为while的条件,因此循环继续。线程B将在此循环中继续“旋转”,直到锁定被释放

最后,当A调用解锁时,标志会变为清除。然后B可以再次测试,并且false结束循环。

© www.soinside.com 2019 - 2024. All rights reserved.