来举个例子,设想场景中有三个人物,小强,小强妈妈,小强爸爸。
星期天的早上,小强妈妈叫小强起床,并给小强做好了饭菜,倒好了水。小强妈妈任务结束,去跳广场舞。
小强平日里,都是先吃饭,后喝水。小强爸爸,看见小强喝水了,就会认为小强吃完饭了,就会默默的去洗碗 。
小强妈妈,并不关心小强是先喝水还是先吃饭,她已经出去玩了。小强自己也不在意先喝水还是先吃饭,总之吃饱喝足即可。
小强先吃饭还是先喝水却能够影响到他的爸爸,想要让爸爸保证正确行为,小强必须按照顺序,先吃饭后喝水。
有一些特殊情况,小强需要接电话,接电话时可以喝水,但吃饭就不方便了,所以小强就会先喝水,然后再吃饭,这样更节省时间。
但这种行为不会影响到小强和她的妈妈,但对于小强爸爸,就悲剧了,看见儿子喝水了,但碗里还是有饭无法洗。
恩恩,很多事情就是这样啊,我们自己不关心做事顺序,有些人也不关心我们做事的顺序,我们更关注结果。但对于依据我们行为顺序做出决策的人就截然不同。
就像打扑克一样,同样一副牌能否赢,很大程度上依赖于你出牌的顺序,哈哈。顺序可以影响结果,也可以对结果没有影响。
相信大家已经有了感性的认识,接下来把术语带进来。
那么设想这三个人,分别是三个线程,A线程,B线程,C线程。
如果只有A,B两个线程,A要执行两个任务,B不于A交互,A,B只关心结果,那么编译器和处理器可以对A的任务任意重排,以图节省CPU时间。
但是,我们的编译器和CPU不会聪明到发现C依赖于A的顺序,而我们编译器和CPU第一要务仍然是重排指令以提供效率,节省时间。这样我们的程序就出现了非预期的各种结果。
补充说明一下:
我们CPU不仅是会重排指令啊,它会的更多,比如分支预测技术,缓存技术等等。
分支预测:猜测下一条指令是进入if还是进入else,stackoverflow上排名第一的问题http://stackoverflow.com/questions/11227809/why-is-processing-a-sorted-array-faster-than-an-unsorted-array。
缓存技术:我们说快速排序比归并排序好在哪里,在算法学术层面两个算法的复杂度是一样的,那为什么快速排序好,就是因为快速排序有很强的缓存优势。
这样问题就来了,处理器和编译器需要你的帮助,不然也许会错误的优化你的代码。哈哈memory_barrier就是干这个的,提示编译器和CPU不要重排你的代码。
memory_barrier主要分为两大类,一类是编译器的,一类是CPU的。其中CPU的barrier由分为几种读屏障,写屏障等等,实际上是为了防止读写内存乱序的。
CPU主要也分两大类,一类是强序的(不改变代码的执行顺序),一类是弱序的(改变代码的执行顺序)。
对于强序CPU,我们只需要编译器barrier,对于弱序CPU我们同时需要两者。
好的,第一个问题终于谈完啦,在谈谈happen-before吧。
首先,排除另一个容易混淆的概念,happening-before,一个事件A发生在B事件之前,如下示意图
----------------------------------------------
(A start)|-------||----| (A end)
(B start) |----| (B end)
----------------------------------------------
A先发生,B后发生,当B需要A的结果,B可见A的结果(它需要的部分已经可见),并且A的后续操作对B无影响。A happen-before B,but not happening before B
---------------------------------------------
(A start)|-----------| (A end)
(B start) |----|(B end)
----------------------------------------------
A先发生,B后发生,并且(注意并且保持学术严谨)A的结果对B可见,通常应该是必然可见的.A happen-before B,and happening before B
----------------------------------------------
(B start)|------------||--| (B end)
(A start) |----------| (A end)
---------------------------------------------
B先发生,A后发生,当B需要A的结果,B可见A的结果,那么A happen before B,but not happening before B。
----------------------------------------------
(A start)|---------| (A end)
(B start) |-------||-----| (B end)
---------------------------------------------
A先发生,B后发生,当B需要A的结果,B可见A的结果,那么A happen-before B, and happening-before B.
-----------------------------------------------
(A start)|------------||----| (A end)
(B start) |----------| (B end)
-----------------------------------------------
A先发生,B后发生,当B需要A的结果,B可见A的结果(它需要的部分已经可见),并且A的后续操作对B无影响。A happen-before B, and happening-before B.
---------------------------------------------
(A start) |--------||----| (A end)
(B start) |----------| (B end)
---------------------------------------------
B先发生,A后发生,当B需要A的结果,B可见A的结果(它需要的部分已经可见),并且A的后续操作对B无影响。A happen-before B, but not happening-before B.
如果反推回去,我们会发现A happen before B 什么也推不出来,A可以发生在B之前或之后,结束在之前或之后。
我个人的理解,无论上述操作在时间关系上如何,我们铭记一点,A happen-before B,那么A产生的的结果(B需要那部分),在B需要时,对B可见。换个角度将A必须能够的影响到B。
A已经发生了,但B不认为A已经发生了,A对B无影响,那么关系不成了。
就算B先发生,但A确实对B产生了影响,就如同A先发生一样,关系依旧成立。
在补充一句话,我觉得说的比较有道理:
理解内存栅栏和happen-before关系有什么具体应用啊?那么我们可能在无锁编程中大量应用,另外在锁编程中,memory barrier一样是有用的。在单例模式并不简单那篇文章中,提到memory barrier应用。
所谓的“无锁数据结构"其实不是真的没有锁,只是没有使用普通的那种锁,一般用原子操作(原子增、减、比较和交换CAS等)修改关键计数器、指针等,利用内存屏障避免乱序执行问题,在读写互斥时一般采用忙等待(自旋锁)。无锁数据结构的适用性比普通的有锁结构小,一般用于在读写冲突概率较小,而对性能又要求很高的场合。
最后,本文正确性,有待考证。我不希望被转载,有缘人看之,取其精华,弃其糟粕,谢谢。欢迎交流