浅谈memory barrier和happen before

来源:互联网 发布:python 捕获ctrl c 编辑:程序博客网 时间:2024/06/16 11:24

来举个例子,设想场景中有三个人物,小强,小强妈妈,小强爸爸。

星期天的早上,小强妈妈叫小强起床,并给小强做好了饭菜,倒好了水。小强妈妈任务结束,去跳广场舞。

小强平日里,都是先吃饭,后喝水。小强爸爸,看见小强喝水了,就会认为小强吃完饭了,就会默默的去洗碗 。

小强妈妈,并不关心小强是先喝水还是先吃饭,她已经出去玩了。小强自己也不在意先喝水还是先吃饭,总之吃饱喝足即可。

小强先吃饭还是先喝水却能够影响到他的爸爸,想要让爸爸保证正确行为,小强必须按照顺序,先吃饭后喝水。

有一些特殊情况,小强需要接电话,接电话时可以喝水,但吃饭就不方便了,所以小强就会先喝水,然后再吃饭,这样更节省时间。

但这种行为不会影响到小强和她的妈妈,但对于小强爸爸,就悲剧了,看见儿子喝水了,但碗里还是有饭无法洗。


恩恩,很多事情就是这样啊,我们自己不关心做事顺序,有些人也不关心我们做事的顺序,我们更关注结果。但对于依据我们行为顺序做出决策的人就截然不同。

就像打扑克一样,同样一副牌能否赢,很大程度上依赖于你出牌的顺序,哈哈。顺序可以影响结果,也可以对结果没有影响


相信大家已经有了感性的认识,接下来把术语带进来。


那么设想这三个人,分别是三个线程,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等)修改关键计数器、指针等,利用内存屏障避免乱序执行问题,在读写互斥时一般采用忙等待(自旋锁)。无锁数据结构的适用性比普通的有锁结构小,一般用于在读写冲突概率较小,而对性能又要求很高的场合。



最后,本文正确性,有待考证。我不希望被转载,有缘人看之,取其精华,弃其糟粕,谢谢。欢迎交流

0 0