最近也是一直准备秋招没怎么写博客,面试的情况也算一般吧,这周面完了百度和360,也不知道什么时候有结果,心累。
之前在看 《C++并发编程实战》的时候以及在C++并发学习/)的过程中就有学习过了,现在抽空把它总结一下。
在多核CPU,典型的系统架构如下:
它包含有2个CPU核,每个CPU核有一个私有的32KB 的 L1 cache,两个CPU 核共享 1MB的 L2 cache 以及 512MB的主存。
在这个内存模型下,cpu写数据并不是立即写入RAM中,而是写入L1 cache,再从L1 cache存入(store) RAM中,读数据也是先从L1 cache中读,读不到再从RAM中读,这种读写数据的模式是能够提高数据存取效率的,但是在一些特殊情况下会导致程序出错,考虑以下这个例子。
|
|
表面上看,r1 == r2 == 0 这种输出是不可能出现的,然而,有一种可能性是,由于r1不依赖于x,编译器可以把r1 = y这步操作调整到x = 1这步操作之前,同样,r2 = x这步操作可以调整到y = 1这步操作之前,这样一来,core 1可以先读取L1 cache中的y的值,core 2 才执行 y = 1的赋值操作,同理,r2 = x这步操作也可以在x = 1这步赋值操作之前执行,这时候就会出现r1 == r2 == 0的输出结果。
Memory Barrier
LoadLoad
StoreStore
LoadStore
StoreLoad
内存屏障用来代替互斥锁,既能保证程序的正确性,又能尽可能地提高程序执行效率
LoadLoad
LoadLoad 这种内存栅栏(memory barrier),顾名思义,就是阻止栅栏后面的load操作被调整到栅栏前面的load操作之前,类似于 git pull 或者 svn update 操作
LoadLoad 的主要作用是防止程序加载已经过期的数据,考虑以下代码:
|
|
LOADLOAD_FENCE 在其中的作用是阻止读取Value这步操作被reorder到读取IsPublished这步操作之前,这样,只有在IsPublished置位后,才会去读取Value的值。
StoreStore
类似于LoadLoad,StoreStore 这种内存栅栏用于阻止栅栏后面的store操作被调整到栅栏前面的store操作之前,类似于git push或者svn commit操作
同理,StoreStore可以避免将过期的数据写入内存。
|
|
LoadStore
LoadStore 内存栅栏用于保证所有在这个栅栏之前的load操作一定会在这个栅栏之后的store操作之前执行。例如:
|
|
在这里,Value = 1 这步操作可以被提前到读取X的值这步操作之前,之所以允许这种优化,是因为有时候在L1 cache中没有缓存X的值,而已经缓存了Value=1这步操作,这时候先执行store再执行load效率会更高。然而,LoadStore这种栅栏可以阻止这种情况的发生。
StoreLoad
StoreLoad 用于保证所有在这个栅栏之前的store操作一定会在这个栅栏之后的load操作之前执行,可以认为这是svn或者git中用户本地代码目录与central repository之间的一次同步操作
StoreLoad 可以解决前文所说的r1==r2==0的问题,考虑将程序改成如下这种形式。
|
|
在这种情况下,r1 == r2 == 0这个情况是不会出现的。
Acquire与Release语义
Acquire与Release是无锁编程中最容易混淆的两个原语,它们是线程之间合作进行数据操作的关键步骤。在这里,借助前面对memory barrier的解释,对acquire与release的语义进行阐述。
acquire
本质上是read-acquire,它只能应用在从RAM中read数据这种操作上,它确保了所有在acquire之后的语句不会被调整到它之前执行。
用上面的memory barrier来描述,acquire等价于LoadLoad加上LoadStore栅栏。
release
release本质上是write-release,它只能应用在write数据到RAM中,它确保了所有在release之前的语句不会被调整到它之后执行。
用上面的memory barrier来描述,release等价于LoadStore加上StoreStore栅栏。
互斥锁(mutex)
借助acquire与release语义,我们再重新来看一下互斥锁(mutex)如何用acquire与release来实现,实际上,mutex正是acquire与release这两个原语的由来,acquire的本意是acquire a lock,release的本意是release a lock,因此,互斥锁能保证被锁住的区域内得到的数据不会是过期的数据,而且所有写入操作在release之前一定会写入内存。
C++ 11中与memory order相关的同步操作
默认,排序一致:
- memory_order_seq_cst
自由序列:
- memory_order_relaxed,
获取-释放:
- memory_order_acquire
- memory_order_consume
- memory_order_release
- memory_order_acq_rel