提问者:小点点

通过使用原子操作作为高级同步设施的基础,对非原子操作进行排序


我首先引用安东尼·威廉姆斯(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()操作是一个关于标志的循环。test_and_set()使用d::memory_order_acquire排序,而解锁()则是以std:: memory_order_release排序对标志. Clear()的调用。当第一个线程调用lock()时,标志最初是清除的,因此第一次调用test_and_set()将设置标志并返回false,表示该线程现在拥有锁,并终止循环。然后线程可以自由修改任何受互斥锁保护的数据。此时调用lock()的任何其他线程都会发现标志已经设置,并将在test_and_set()循环中被阻塞。

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

问:如果只有两个线程,线程A有对象< code>m1第一次调用< code>lock(),线程B有对象< code>m1第一次调用< code>lock(),在线程A中< code>m1调用< code>unlock()之前,为什么< code > flag . test _ and _ set(STD::memory _ order _ acquire)在< code >时得到true而不是false(初始值)

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


共3个答案

匿名用户

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

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

匿名用户

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

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

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

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

匿名用户

只有一个std::atomic_flag。在任何时候,它要么被设置(true),要么被清除(false)。

std::atomic_flag::test_and_set定义为

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

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

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