我首先引用安东尼·威廉姆斯(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
。
获取
和发布
语义与其他(受保护的)资源相关,此处未显示。特别是,不要在锁定后或解锁前移动通道。原子操作本身是完全有序的。
由于操作是完全有序的,因此两个线程以相同的顺序看到假设的顺序 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
结束循环。