使用atomic_flag自旋锁进行内存排序

时间:2013-02-09 20:54:12

标签: c++ c++11 atomic

我正在努力熟悉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作为旧值 - &gt;线程1已获得锁定 但随后线程2出现并尝试相同。现在因为没有发生“存储同步”(release,seq_cst_acq_rel),所以线程1的spin_lock存储应该是轻松的类型。
但是由此得出它不能与线程2的spin_lock读取同步。这应该使线程2能够从spin_lock读取值0,从而获得锁定 我的错误在哪里?

3 个答案:

答案 0 :(得分:5)

您的错误在于忘记spin_lockatomic_flag,因此test_and_set是原子操作。需要memory_order_acquirememory_order_release来防止读取在锁定操作之前迁移到迁移到解锁之后的写入。锁本身受原子性保护,原子性始终包括可见性。

答案 1 :(得分:2)

对原子标志的

test_and_set操作被指定为具有特殊特征的读 - 修改 - 写操作,其中一个是:

  

原子读 - 修改 - 写操作应始终读取与读 - 修改 - 写操作相关的写操作之前写入的最后一个值(按修改顺序)。 [n3337§29.3/ 12]

这也是为什么fetch_add有效的原因,而简单的加载操作不需要读取修改顺序中的最新值。

答案 2 :(得分:2)

对于给定的原子变量,它有一个“修改顺序”。一旦线程1 test_and_sets值从0到1,线程2就不可能看到0。

内存顺序会影响所有其他内存地址的“同步”。如果一个线程使用memory-order_release修改一个原子变量,那么任何读取同一个变量的线程都会通过memory_order_acquire“看到”每个内存改变它发布之前的第一个线程。

获得和释放不是关于原子的。它是关于确保成功锁定自旋锁的每个线程“看到”之前锁定它的每个线程的变化。

修改顺序是使算法无锁的关键。线程1和线程2都试图对同一个变量执行test_and_set,因此根据规则,一个修改“在另一个之前发生”。因为test_and_set“发生在”之前“另一个”进入“进展”,所以至少一个线程必须始终取得进展。这是lockfree的定义