我首先引用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有对象m1
在lock()
在线程A中调用m1
之前第一次调用unlock()
,为什么flag.test_and_set(std::memory_order_acquire)
变为true而不是当m1
在线程B中调用lock
函数时,是否为false(初始值)?
我知道发布序列,但构成一个发布序列需要一个原子对象调用std::memory_order_release
的原子操作,并且没有用std::memory_order_release
调用的操作。
acquire
和release
语义与其他(受保护)资源相关,此处未显示。特别是,在锁定之后或解锁之前不要移动访问。原子操作本身是完全有序的。
由于操作是完全有序的,因此两个线程都会以相同的顺序看到您的假设订单A:lock, B:lock, A:unlock
。因此,当线程B调用lock
时,它只能看到来自A的lock
而不是unlock
。
除了具有你想知道的行为之外,线程在彼此之前做事并没有真正意义。 memory_order没有进入这个。它指定了如何围绕原子操作对常规非原子内存访问进行排序。
拥有它的原因是,如果你这样做:
lock();
foo();
unlock();
在两个线程中,一个线程中的foo在锁定之前或解锁之后无法读取或解析相关线程。这与锁定和解锁自身的原子性相结合,给出了我们期望的行为。 (即没有来自foo的并发访问)。
只有一个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
结束循环。