我正在努力熟悉c++11的新内存排序概念,并相信我实际上已经很好地掌握了这些概念,直到我偶然发现了这个旋转锁的实现:
#include <atomic>
namespace JayZ
{
namespace Tools
{
class SpinLock
{
private:
std::atomic_flag spin_lock;
public:
inline SpinLock( void ) : atomic_flag( ATOMIC_FLAG_INIT ) {}
inline void lock( void )
{
while( spin_lock.test_and_set( std::memory_order_acquire ) )
;
}
inline void unlock( void )
{
lock.clear( std::memory_order_release );
}
};
}
}
例如,在http://en.cppreference.com/w/cpp/atomic/atomic_flag
以及《行动中的并发》一书。我还在SO.的某个地方找到了它
但我就是不明白为什么它会起作用
假设线程1调用lock(),test_and_set()返回0,因为旧值-->线程1已经获取了锁
但随后线程2出现并尝试相同的操作。现在,由于没有发生"存储同步"(release,seq_cst_acq_rel),线程1的存储到spin_lock的类型应该是松弛的
但由此可知,它不能与线程2读取spin_lock同步。这样线程2就可以从spin_lock中读取值0,从而也可以获取锁
我的错误在哪里?
您的错误在于忘记了spin_lock
是atomic_flag
,因此test_and_set
是原子操作。需要memory_order_acquire
和memory_order_release
来防止在锁定操作之前读取迁移到或在解锁之后写入迁移到。锁本身受到原子性的保护,原子性总是包括可见性。
对于给定的原子变量,有一个"修改顺序"。一旦线程1 test_and_将值从0设置为1,线程2就不可能看到0。
内存顺序会影响所有其他内存地址的"同步"方式如果一个线程用memory-order_release修改原子变量,那么任何用memory_order_aquire读取同一变量的线程都会"看到"第一个线程在释放前所做的每一次内存更改。
获取和释放与原子无关。这是关于确保每个成功锁定spinlock的线程"看到"以前锁定它的每个线程的变化。
修改顺序是使算法无锁定的关键。线程1和线程2都试图对同一个变量进行test_and_set,因此根据规则,一个修改"先于"另一个修改。因为test_and_set"发生在"另一个线程"进展之前",所以至少有一个线程必须始终取得进展。这是无锁定的定义
原子标志上的test_and_set
操作被指定为具有特殊特性的读-修改-写操作,其中之一是:
原子读取-修改-写入操作应始终读取与读取-修改写入操作相关联的写入之前写入的最后一个值(按修改顺序)。[n3337§29.3/12]
例如,这也是fetch_add
工作的原因,而读取修改顺序中的最新值不需要简单的加载操作。