C++11引入了atomic和memory order支持,使得写可移植的无锁数据结构成为可能。
其中memory order支持两种形式的API,一种是在操作一个atomic变量时指定memory order,另外一种是单独指定memory order的atomic_thread_fence()函数调用.
memory order主要有以下几种:类似于memory_order_acquire,但是只对有依赖关系的内存。意思是别的CPU执行了memory_order_release操作,而其他依赖于这个atomic变量的内存会被执行memory_order_consume的CPU看到。这个操作是C++特有的,x86也不支持这种类型的memory order,不清楚其他种类的cpu是否支持,这还涉及到编译器是否支持这种细粒度控制,也许它直接按memory_order_acquire来处理。
memory_order_acquire执行memory_order_acquire的cpu,可以看到别的cpu执行memory_order_release为止的语句对内存的修改。执行memory_order_acquire这条指令犹如一道栅栏,这条指令没执行完之前,后续的访问内存的指令都不能执行,这包括读和写。memory_order_release执行memory_order_release的cpu,在这条指令执行前的对内存的读写指令都执行完毕,这条语句之后的对内存的修改指令不能超越这条指令优先执行。这也象一道栅栏。在这之后,别的cpu执行memory_order_acquire,都可以看到这个cpu所做的memory修改。
memory_order_acq_rel是memory_order_acquire和memory_order_release的合并,这条语句前后的语句都不能被reorder。memory_order_seq_cst这是比memory_order_acq_rel更加严格的顺序保证,memory_order_seq_cst执行完毕后,所有其cpu都是确保可以看到之前修改的最新数据的。如果前面的几个memory order模式允许有缓冲存在的话,memory_order_seq_cst指令执行后则保证真正写入内存。一个普通的读就可以看到由memory_order_seq_cst修改的数据,而memory_order_acquire则需要由memory_order_release配合才能看到,否则什么时候一个普通的load能看到memory_order_release修改的数据是不保证的。 x86的memory orderx86的memory order是一种strong memory order,它保证:
LoadLoad是顺序的 一个cpu上前后两条load指令是顺序执行的,前面一条没执行完毕,后面一条不能执行StoreStore是顺序的 一个cpu上前后两条store指令是顺序执行的,前面一条没执行完毕,后面一条不能执行LoadStore 一个cpu上前面一条是Load指令,这条指令没执行完毕,后面一条store不能执行x86不保证StoreLoad的顺序,一条Store指令在前,后面一条不相关的load指令可以先执行。因为这个顺序的不保证,导致Peterson lock实际上需要使用mfence指令才能在x86上实现。
x86上很多原子操作需要使用lock前缀或者隐含lock语义,例如xchg指令。这个lock语义是上面memory_order_seq_cst的语义,是一个full memory barrier。相对来说在x86上的memory order 比较容易使用,但是性能有所损失,例如上面的LoadLoad是顺序执行的,但是如果第一个Load因为cache不命中,就引起从内存Load而导致的延迟,虽然第二个Load是可以cache命中的,但是因为第一个Load的delay,影响到第二个Load的执行,继而导致后续运算都delay。
考虑以下代码,是用来防止x86上的StoreLoad的reorder问题。
#include<atomic> using namespace std; atomic_int a; int j; int func() { int n; a.store(1,memory_order_acquire); n = j; return n; }程序想先给a赋值1,然后读变量j的值。如果不强加memory order,则读j的指令可能会被cpu先执行,
这是用clang++编译后的结果:
可以看到,clang++并没有对memory order进行约束。
gcc编译的结果:
明显可以看到在中间加入了mfence指令,防止前后两条指令执行乱序。
从编译情况来看gcc正确理解了程序的意图,而clang++貌似理解错了。