zoukankan      html  css  js  c++  java
  • 还是说Memory Model,gcc的__sync_synchronize真是太坑爹了

    还是说Memory Model,gcc的__sync_synchronize真是太坑爹了!

    嗯,还是说可见性的问题。由于CPU和编译器的乱序执行功能,我们经常不得不在代码中手动插入memory barrier。如果你还不清楚memory barrier是什么,那么请先读这个 http://en.wikipedia.org/wiki/Memory_barrier

    假如你已经了解它了,那么具体怎么用呢?怎么在代码中插入一个memory barrier ? 用哪个函数?

    gcc的手册中有一节叫做”Built-in functions for atomic memory access”,然后里面列举了这样一个函数:

    __sync_synchronize (…) 

    This builtin issues a full memory barrier. 

    来,我们写段代码试下:

    int main(){
      __sync_synchronize();
      return 0;
    }

    然后用gcc4.2编译,

    # gcc -S -c test.c

    然后看对应的汇编代码,

    ? View Code ASM
    main:
      pushq %rbp
      movq %rsp, %rbp
      movl $0, %eax
      leave
      ret

    嗯?Nothing at all !!! 不信你试一试,我的编译环境是Freebsd 9.0 release, gcc (GCC) 4.2.1 20070831 patched [FreeBSD]。

    好,我换个高版本的gcc编译器试一试,gcc46 (FreeBSD Ports Collection) 4.6.3 20120113 (prerelease)

    ? View Code ASM
    main:
     
    pushq %rbp
     
    movq %rsp, %rbp
     
    mfence
     
    movl $0, %eax
     
    popq %rbp
     
    ret

    看,多了一行,mfence。

    怎么回事呢?这是gcc之前的一个BUG:http://gcc.gnu.org/bugzilla/show_bug.cgi?id=36793 。 2008年被发现,然后修复的。其实它之所以是一个BUG,关键在于gcc的开发者很迷惑,mfence在x86 CPU上到底有没有用?有嘛用?

    那么mfence到底能不能提供我们想要的结果呢?

    Intel的手册中有这么一段我一直没太读懂:

    “Processors are free to fetch and cache data speculatively from regions of system memory that use the WB, WC, and WT memory types. This speculative fetching can occur at any time and is not tied to instruction execution. Thus, it is not ordered with respect to executions of the MFENCE instruction; data can be brought into the caches speculatively just before, during, or after the execution of an MFENCE instruction. Processors are free to fetch and cache data speculatively from regions of system memory that use the WB, WC, and WT memory types. This speculative fetching can occur at any time and is not tied to instruction execution. Thus, it is not ordered with respect to executions of the MFENCE instruction; data can be brought into the caches speculatively just before, during, or after the execution of an MFENCE instruction.”

    但是在关于Memory Ordering的章节又说: 

    “Reads cannot pass earlier MFENCE instructions 

    Writes cannot pass earlier MFENCE instructions. 

    MFENCE instructions cannot pass earlier reads or writes” 

    综合起来的意思是,如果代码是这样,

    READ A 
    MFENCE 
    READ B

    那么可能会成为这样

    READ A 
    Speculative READ B 
    MFENCE

    但是不会成为这样

    Speculative READ B 
    READ A 
    MFENCE

    也就是说,Speculative READ可以穿越mfence,但是无法走的更远了,不能再穿越前面的读写的操作。所以无论如何,A、B的读取顺序还是被严格保持的。不知道我的理解对不对。

    但是这些只是针对单CPU而言。多CPU(包括超线程和多核)的时候,如果还是用Locked instructions,那么没问题。否则手册里的规则没有特别提到mfence,而是说了这么一句,”Memory ordering obeys causality (memory ordering respects transitive visibility).” Causality 也是一种relaxed Model,我不是很能理解。只看到一句通俗点的解释,” If i see it and tell you about it , then you will see it too.” 

    这个改动对JAVA社区的影响巨大。JVM通过插入mfence指令,使得volatile变量的写入操作,能得到sequential consistency。于是Java社区现在就只有两条路,要么修改语言规范,要么把mfence换成代价更高的xchg指令。David Dice(transactional memory的TL/TL2算法的发明人)的这篇日志http://blogs.oracle.com/dave/entry/java_memory_model_concerns_on 那写的是相当的悲苦啊。

    如果实在想在代码中使用membar,那么最好是认准某个编译器和某个平台。否则,非常推荐intel tbb。tbb:: atomic_fence()。

    嗯,最后说下C/C++中的volatile。如果非要去咬文嚼字的看标准,那么这个关键字对于多线程同步简直一点帮助都没有。那么实际呢?我们发现很多人在C/C++中用这个关键字,而且,他们不是在写驱动,就是普通的用户态的代码。他们真的都错了吗?

    看看这段神奇的代码:

    http://software.intel.com/en-us/articles/single-producer-single-consumer-queue/

    这段代码中,所有的membar都可以去掉,无非是告诉编译器,别乱搞。volatile关键字,在具体某个编译器中的含义,要远比ISO标准中说的要多。

    这是我今天用tbb写的一个Singleton:

    ? View Code CPP
    class StatusCodeManager{
    public:
      static StatusCodeManager& instance(){
        if( !value ) {
          StatusCodeManager* tmp = new StatusCodeManager();
          if( value.compare_and_swap(tmp,NULL)!=NULL )
            // Another thread installed the value, so throw away mine.
            delete tmp;
        }
     
        return *value;
      }
    protected:
      StatusCodeManager();
    private:
      static tbb::atomic<StatusCodeManager*> value;
    };

    虽然无锁化了,但是并不保证构造函数只执行一次。所以我一直在愁,value->init();插在哪里好呢?

    (p.s. 今天找paper的时候看到一篇让我满眼冒金星的神文:《Mathematizing C++ Concurrency》 http://www.cl.cam.ac.uk/~pes20/cpp/popl085ap-sewell.pdf 第一作者是剑桥的某在读博士。)

  • 相关阅读:
    HTML5 & CSS3编程入门经典 ((美)Rob Larsen) pdf扫描版
    HTML5+JavaScript动画基础 完整版 中文pdf扫描版
    HTML5程序开发范例宝典 完整版 (韩旭等著) 中文pdf扫描版
    HTML5从入门到精通(明日科技) 中文pdf扫描版
    HTML5秘籍(第2版) 中文pdf扫描版
    HTML5与CSS3实例教程(第2版) 附源码 中文pdf扫描版
    windows下一分钟配置ngnix实现HLS m3u8点播
    linux下搭建生成HLS所需的.ts和.m3u8文件
    使用Flash Media Server(FMS)录制mp4格式的视频
    FMS 客户端带宽计算、带宽限制
  • 原文地址:https://www.cnblogs.com/liyulong1982/p/5500683.html
Copyright © 2011-2022 走看看