C++ 原子标志自旋锁的内存排序

C++ 原子标志自旋锁的内存排序,c++,c++11,atomic,C++,C++11,Atomic,我正试图熟悉c++11的新内存排序概念,并相信我实际上已经很好地掌握了这些概念,直到我偶然发现了自旋锁的这个实现: #include <atomic> namespace JayZ { namespace Tools { class SpinLock { private: std::atomic_flag spin_lock; public: inline

我正试图熟悉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 );
            }
        };
    }
}
#包括
也在《行动中的并发性》一书中。我在某处也找到了它

但我就是不明白为什么它会起作用
假设线程1调用lock(),test_和_set()返回0,因为旧值-->线程1已获得锁。
但是线程2出现并尝试相同的操作。现在,由于没有发生“存储同步”(release,seq_cst_acq_rel),线程1的存储到spin_锁应该是relaxed类型。
但由此可知,它不能与线程2读取的自旋锁同步。这将使线程2能够从spin_lock读取值0,从而获得锁。

我的错误在哪里?

你的错误在于忘记了
自旋锁
是一个
原子标志
,因此
测试和设置
是一个原子操作。需要
内存\u顺序\u获取
内存\u顺序\u释放
,以防止读取在锁定操作之前迁移到,或写入在解锁之后迁移到。锁本身受原子性保护,原子性始终包括可见性。

测试和设置
原子标志上的操作被指定为具有特殊特征的读修改写操作,其中之一是:

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


这也是为什么
fetch\u add
可以工作的原因,例如,读取修改顺序中的最新值不需要简单的加载操作。

对于给定的原子变量,有一个“修改顺序”。一旦线程1测试_和_将值从0设置为1,线程2就不可能看到0

内存顺序会影响所有其他内存地址的“同步”方式。如果一个线程使用内存顺序释放修改原子变量,那么任何使用内存顺序获取读取同一变量的线程都会“看到”在释放前对第一个线程所做的每一次内存更改

获取和释放与原子无关。这是关于确保成功锁定自旋锁的每个线程“看到”之前锁定它的每个线程的更改


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

但是如果你说的是真的。这难道不意味着,对一个原子变量的任何松弛存储都会立即对另一个线程可见吗?这绝对不是事实,不是吗?对原子变量的任何操作都是原子的,并且对对同一个原子变量执行原子操作的任何其他线程都可以立即看到。原子变量就是这样做的。你可以说,原子变量实际上做了很多人错误地认为易变变量会做的事情。@bames53:这不是可见性问题,而是排序问题。没有什么要求这些操作实际按照代码指定的顺序进行。尽管如此,它们在执行时是原子的,并且在执行后对其他原子操作可见。(正如答案所解释的,这就是为什么如果需要以特定方式与其他访问进行交互,就需要指定内存顺序。)@bames53:没有“存储的顺序”这样的说法。这些商店秩序混乱。这并不意味着它们必须在两个可能的订单中的一个订单中进行,而是意味着它们不是在一个订单中进行的。(您所做的假设是无效的,即存在某种全局时间概念,并且每次加载或存储都发生在该全局时间线的某个定义点。不需要该时间模型。)除非您特别强制排序,否则对不同原子变量的操作是无序的。