C/C++ Volatile关键词深度剖析

12月 2nd, 2013

1    背景    1

2    Volatile:易变的    1

2.1    小结    2

3    Volatile:不可优化的    3

3.1    小结    4

4    Volatile:顺序性    4

4.1    happens-before    6

4.2    小结    7

5    Volatile:Java增强    8

6    Volatile的起源    9

7    参考资料    9

 

  1. 背景

前几天,发了一条如下的微博 (关于C/C++ Volatile关键词的使用建议):


 

此微博,引发了朋友们的大量讨论:赞同者有之;批评者有之;当然,更多的朋友,是希望我能更详细的解读C/C++ Volatile关键词,来佐证我的微博观点。而这,正是我写这篇博文的初衷:本文,将详细分析C/C++ Volatile关键词的功能 (有多种功能)、Volatile关键词在多线程编程中存在的问题、Volatile关键词与编译器/CPU的关系、C/C++ Volatile与Java Volatile的区别,以及Volatile关键词的起源,希望对大家更好的理解、使用C/C++ Volatile,有所帮助。

 

Volatile,词典上的解释为:易失的;易变的;易挥发的。那么用这个关键词修饰的C/C++变量,应该也能够体现出”易变”的特征。大部分人认识Volatile,也是从这个特征出发,而这也是本文揭秘的C/C++ Volatile的第一个特征。

 

 

  1. Volatile:易变的

在介绍C/C++ Volatile关键词的”易变”性前,先让我们看看以下的两个代码片段,以及他们对应的汇编指令 (以下用例的汇编代码,均为VS 2008编译出来的Release版本):

 

  • 测试用例一:非Volatile变量

    volatile1

    b = a + 1;这条语句,对应的汇编指令是:lea ecx, [eax + 1]。由于变量a,在前一条语句a = fn(c)执行时,被缓存在了寄存器eax中,因此b = a + 1;语句,可以直接使用仍旧在寄存器eax中的a,来进行计算,对应的也就是汇编:[eax + 1]。

  • 测试用例二:Volatile变量

volatile2

与测试用例一唯一的不同之处,是变量a被设置为volatile属性,一个小小的变化,带来的是汇编代码上很大的变化。a = fn(c)执行后,寄存器ecx中的a,被写回内存:mov dword ptr [esp+0Ch], ecx。然后,在执行b = a + 1;语句时,变量a有重新被从内存中读取出来:mov eax, dword ptr [esp + 0Ch],而不再直接使用寄存器ecx中的内容。

  1. 小结

从以上的两个用例,就可以看出C/C++ Volatile关键词的第一个特性:易变性。所谓的易变性,在汇编层面反映出来,就是两条语句,下一条语句不会直接使用上一条语句对应的volatile变量的寄存器内容,而是重新从内存中读取。volatile的这个特性,相信也是大部分朋友所了解的特性。

 

在了解了C/C++ Volatile关键词的”易变”特性之后,再让我们接着继续来剖析Volatile的下一个特性:”不可优化”特性。

 

  1. Volatile:不可优化的

与前面介绍的”易变”性类似,关于C/C++ Volatile关键词的第二个特性:”不可优化”性,也通过两个对比的代码片段来说明:

 

  • 测试用例三:非Volatile变量

    volatile4

    在这个用例中,非volatile变量a,b,c全部被编译器优化掉了 (optimize out),因为编译器通过分析,发觉a,b,c三个变量是无用的,可以进行常量替换。最后的汇编代码相当简介,高效率。

  • 测试用例四:Volatile变量

    volatile3

    测试用例四,与测试用例三类似,不同之处在于,a,b,c三个变量,都是volatile变量。这个区别,反映到汇编语言中,就是三个变量仍旧存在,需要将三个变量从内存读入到寄存器之中,然后再调用printf()函数。

 

  1. 小结

从测试用例三、四,可以总结出C/C++ Volatile关键词的第二个特性:“不可优化”特性。volatile告诉编译器,不要对我这个变量进行各种激进的优化,甚至将变量直接消除,保证程序员写在代码中的指令,一定会被执行。相对于前面提到的第一个特性:”易变”性,”不可优化”特性可能知晓的人会相对少一些。但是,相对于下面提到的C/C++ Volatile的第三个特性,无论是”易变”性,还是”不可优化”性,都是Volatile关键词非常流行的概念。

 

  1. Volatile:顺序性

 

C/C++ Volatile关键词前面提到的两个特性,让Volatile经常被解读为一个为多线程而生的关键词:一个全局变量,会被多线程同时访问/修改,那么线程内部,就不能假设此变量的不变性,并且基于此假设,来做一些程序设计。当然,这样的假设,本身并没有什么问题,多线程编程,并发访问/修改的全局变量,通常都会建议加上Volatile关键词修饰,来防止C/C++编译器进行不必要的优化。但是,很多时候,C/C++ Volatile关键词,在多线程环境下,会被赋予更多的功能,从而导致问题的出现。

 

回到本文背景部分我的那篇微博,我的这位朋友,正好犯了一个这样的问题。其对C/C++ Volatile关键词的使用,可以抽象为下面的伪代码:

v5

这段伪代码,声明另一个Volatile的flag变量。一个线程(Thread1)在完成一些操作后,会修改这个变量。而另外一个线程(Thread2),则不断读取这个flag变量,由于flag变量被声明了volatile属性,因此编译器在编译时,并不会每次都从寄存器中读取此变量,同时也不会通过各种激进的优化,直接将if (flag == true)改写为if (false == true)。只要flag变量在Thread1中被修改,Thread2中就会读取到这个变化,进入if条件判断,然后进入if内部进行处理。在if条件的内部,由于flag == true,那么假设Thread1中的something操作一定已经完成了,在基于这个假设的基础上,继续进行下面的other things操作。

 

通过将flag变量声明为volatile属性,很好的利用了本文前面提到的C/C++ Volatile的两个特性:”易变”性;”不可优化”性。按理说,这是一个对于volatile关键词的很好应用,而且看到这里的朋友,也可以去检查检查自己的代码,我相信肯定会有这样的使用存在。

 

但是,这个多线程下看似对于C/C++ Volatile关键词完美的应用,实际上却是有大问题的。问题的关键,就在于前面标红的文字:由于flag = true,那么假设Thread1中的something操作一定已经完成了。flag == true,为什么能够推断出Thread1中的something一定完成了?其实既然我把这作为一个错误的用例,答案是一目了然的:这个推断不能成立,你不能假设看到flag == true后,flag = true;这条语句前面的something一定已经执行完成了。这就引出了C/C++ Volatile关键词的第三个特性:顺序性。

 

同样,为了说明C/C++ Volatile关键词的”顺序性”特征,下面给出三个简单的用例 (注:与上面的测试用例不同,下面的三个用例,基于的是Linux系统,使用的是”GCC: (Debian 4.3.2-1.1) 4.3.2″):

 

  • 测试用例五:非Volatile变量

    v9

    一个简单的示例,全局变量A,B均为非volatile变量。通过gcc O2优化进行编译,你可以惊奇的发现,A,B两个变量的赋值顺序被调换了!!!在对应的汇编代码中,B = 0语句先被执行,然后才是A = B + 1语句被执行。

    在这里,我先简单的介绍一下C/C++编译器最基本优化原理:保证一段程序的输出,在优化前后无变化。将此原理应用到上面,可以发现,虽然gcc优化了A,B变量的赋值顺序,但是foo()函数的执行结果,优化前后没有发生任何变化,仍旧是A = 1;B = 0。因此这么做是可行的。

  • 测试用例六:一个Volatile变量

    v10

    此测试,相对于测试用例五,最大的区别在于,变量B被声明为volatile变量。通过查看对应的汇编代码,B仍旧被提前到A之前赋值,Volatile变量B,并未阻止编译器优化的发生,编译后仍旧发生了乱序现象。

    如此看来,C/C++ Volatile变量,与非Volatile变量之间的操作,是可能被编译器交换顺序的

    通过此用例,已经能够很好的说明,本章节前面,通过flag == true,来假设something一定完成是不成立的。在多线程下,如此使用volatile,会产生很严重的问题。但是,这不是终点,请继续看下面的测试用例七。

  • 测试用例七:两个Volatile变量

    v11

    同时将A,B两个变量都声明为volatile变量,再来看看对应的汇编。奇迹发生了,A,B赋值乱序的现象消失。此时的汇编代码,与用户代码顺序高度一直,先赋值变量A,然后赋值变量B。

    如此看来,C/C++ Volatile变量间的操作,是不会被编译器交换顺序的


  1. happens-before

 

通过测试用例六,可以总结出:C/C++ Volatile变量与非Volatile变量间的操作顺序,有可能被编译器交换。因此,上面多线程操作的伪代码,在实际运行的过程中,就有可能变成下面的顺序:

v6

由于Thread1中的代码执行顺序发生变化,flag = true被提前到something之前进行,那么整个Thread2的假设全部失效。由于something未执行,但是Thread2进入了if代码段,整个多线程代码逻辑出现问题,导致多线程完全错误。

 

细心的读者看到这里,可能要提问,根据测试用例七,C/C++ Volatile变量间,编译器是能够保证不交换顺序的,那么能不能将something中所有的变量全部设置为volatile呢?这样就阻止了编译器的乱序优化,从而也就保证了这个多线程程序的正确性。

 

针对此问题,很不幸,仍旧不行。将所有的变量都设置为volatile,首先能够阻止编译器的乱序优化,这一点是可以肯定的。但是,别忘了,编译器编译出来的代码,最终是要通过CPU来执行的。目前,市场上有各种不同体系架构的CPU产品,CPU本身为了提高代码运行的效率,也会对代码的执行顺序进行调整,这就是所谓的CPU Memory Model (CPU内存模型)。关于CPU的内存模型,可以参考这些资料:Memory Ordering From WikiMemory Barriers Are Like Source Control Operations From Jeff PreshingCPU Cache and Memory Ordering From 何登成。下面,是截取自Wiki上的一幅图,列举了不同CPU架构,可能存在的指令乱序。

 

mo

 

从图中可以看到,X86体系(X86,AMD64),也就是我们目前使用最广的CPU,也会存在指令乱序执行的行为:StoreLoad乱序,读操作可以提前到写操作之前进行。

 

因此,回到上面的例子,哪怕将所有的变量全部都声明为volatile,哪怕杜绝了编译器的乱序优化,但是针对生成的汇编代码,CPU有可能仍旧会乱序执行指令,导致程序依赖的逻辑出错,volatile对此无能为力。

 

其实,针对这个多线程的应用,真正正确的做法,是构建一个happens-before语义。关于happens-before语义的定义,可参考文章:The Happens-Before Relation。下面,用图的形式,来展示happens-before语义:

 

v7

 

如图所示,所谓的happens-before语义,就是保证Thread1代码块中的所有代码,一定在Thread2代码块的第一条代码之前完成。当然,构建这样的语义有很多方法,我们常用的Mutex、Spinlock、RWLock,都能保证这个语义 (关于happens-before语义的构建,以及为什么锁能保证happens-before语义,以后专门写一篇文章进行讨论)。但是,C/C++ Volatile关键词不能保证这个语义,也就意味着C/C++ Volatile关键词,在多线程环境下,如果使用的不够细心,就会产生如同我这里提到的错误。

 

  1. 小结

 

C/C++ Volatile关键词的第三个特性:”顺序性”,能够保证Volatile变量间的顺序性,编译器不会进行乱序优化。Volatile变量与非Volatile变量的顺序,编译器不保证顺序,可能会进行乱序优化。同时,C/C++ Volatile关键词,并不能用于构建happens-before语义,因此在进行多线程程序设计时,要小心使用volatile,不要掉入volatile变量的使用陷阱之中。

 

  1. Volatile:Java增强

 

在介绍了C/C++ Volatile关键词之后,再简单介绍一下Java的Volatile。与C/C++的Volatile关键词类似,Java的Volatile也有这三个特性,但最大的不同在于:第三个特性,”顺序性”,Java的Volatile有很极大的增强,Java Volatile变量的操作,附带了Acquire与Release语义。所谓的Acquire与Release语义,可参考文章:Acquire and Release Semantics。(这一点,后续有必要的话,可以写一篇文章专门讨论)。Java Volatile所支持的Acquire、Release语义,如下:

 

  • 对于Java Volatile变量的写操作,带有Release语义,所有Volatile变量写操作之前的针对其他任何变量的读写操作,都不会被编译器、CPU优化后,乱序到Volatile变量的写操作之后执行。

  • 对于Java Volatile变量的读操作,带有Acquire语义,所有Volatile变量读操作之后的针对其他任何变量的读写操作,都不会被编译器、CPU优化后,乱序到Volatile变量的读操作之前进行。

 

通过Java Volatile的Acquire、Release语义,对比C/C++ Volatile,可以看出,Java Volatile对于编译器、CPU的乱序优化,限制的更加严格了。Java Volatile变量与非Volatile变量的一些乱序操作,也同样被禁止。

 

由于Java Volatile支持Acquire、Release语义,因此Java Volatile,能够用来构建happens-before语义。也就是说,前面提到的C/C++ Volatile在多线程下错误的使用场景,在Java语言下,恰好就是正确的。如下图所示:

 

v8_new

 

  1. Volatile的起源

 

C/C++的Volatile关键词,有三个特性:易变性;不可优化性;顺序性。那么,为什么Volatile被设计成这样呢?要回答这个问题,就需要从Volatile关键词的产生说起。(注:这一小节的内容,参考自C++ and the Perils of Double-Checked Locking论文的第10章节:volatile:A Brief History。这是一篇顶顶好的论文,值得多次阅读,强烈推荐!)

 

Volatile关键词,最早出现于19世纪70年代,被用于处理memory-mapeed I/O (MMIO)带来的问题。在引入MMIO之后,一块内存地址,既有可能是真正的内存,也有可能被映射到一个I/O端口。相对的,读写一个内存地址,既有可能操作内存,也有可能读写的是一个I/O设备。MMIO为什么需要引入Volatile关键词?考虑如下的一个代码片段:

在此代码片段中,指针p既有可能指向一个内存地址,也有可能指向一个I/O设备。如果指针p指向的是I/O设备,那么(1),(2)中的a,b,就会接收到I/O设备的连续两个字节。但是,p也有可能指向内存,此时,编译器的优化策略,就可能会判断出a,b同时从同一内存地址读取数据,在做完(1)之后,直接将a赋值给b。对于I/O设备,需要防止编译器做这个优化,不能假设指针b指向的内容不变——易变性。

 

同样,代码(3),(4)也有类似的问题,编译器发现将a,b同时赋值给指针p是无意义的,因此可能会优化代码(3)中的赋值操作,仅仅保留代码(4)。对于I/O设备,需要防止编译器将写操作给彻底优化消失了——”不可优化”性。

 

对于I/O设备,编译器不能随意交互指令的顺序,因为顺序一变,写入I/O设备的内容也就发生变化了——”顺序性”。

 

基于MMIO的这三个需求,设计出来的C/C++ Volatile关键词,所含有的特性,也就是本文前面分析的三个特性:易变性;不可优化性;顺序性。

 

  1. 参考资料

[1] Wiki. Volatile variable.

[2] Wiki. Memory ordering.

[3] Scott Meyers; Andrei Alexandrescu. C++ and the Perils of Double-Checked Locking.

[4] Jeff Preshing. Memory Barriers Are Like Source Control Operations.

[5] Jeff Preshing. The Happens-Before Relation.

[6] Jeff Preshing. Acquire and Release Semantics.

[7] 何登成. CPU Cache and Memory Ordering——并发程序设计入门.

[8] Bartosz Milewski. Who ordered sequential consistency?

[9] Andrew Haley. What are we going to do about volatile?

[10] Java Glossary. volatile.

[11] stackoverflow. Why is volatile not considered useful in multithreaded C or C++ programming?

[12] msdn. Volatile fields.

[13] msdn. volatile (C++).

[14] 刘未鹏. 《C++0x漫谈》系列之:多线程内存模型.

标签: , ,
  1. hailong
    12月 2nd, 201318:18

    Volatile 变量和non- Volatile变量 会被编译器优化掉,之前还真不知道!长见识了。如果这样,禁止掉优化嘛,也可以

    • hedengcheng
      12月 2nd, 201318:40

      禁掉优化,你可以试试。debug与release版本的性能差距有多大,很多就是编译器优化的功劳。

      • eagle.dai
        12月 2nd, 201322:01

        #pragma optimize(“”, off)

        #pragma optimize(“”, on)

        VC下面可以禁止一块代码优化,GCC下面也应该有类似的方法

        • eagle.dai
          12月 2nd, 201322:03

          当然,这个只能解决编译器的reordering带了的问题,CPU也会做这种事情

        • hedengcheng
          12月 2nd, 201322:40

          禁用编译器的优化,并不是一个好方法,毕竟,对于代码性能来说,编译器的优化,提升太明显了。

  2. jiayy
    12月 2nd, 201319:12

    如果是gcc编译器,在取flag的地方加上 BARRIER 指令是否就可以保证顺序性了 ?

    #define BARRIER() do{ asm volatile(“” ::: “memory”);}while(0)

    • hedengcheng
      12月 2nd, 201322:38

      barrier是可以的。不过,你的这个是compiler barrier,更强一点的话,应该同时加上CPU barrier,例如mfence指令。

      • Yatao Li
        12月 3rd, 201300:57

        lock xor eax, eax比mfence快

  3. zz
    12月 2nd, 201321:09

    赞一下!~不过最后Java volatile 图片中左右两边acquire和release是不是弄反了啊~左边写flag应该是release,右边读flag应该是acquire吧~ 🙂

    • hedengcheng
      12月 2nd, 201322:37

      错了,谢谢指正!

  4. Thomson
    12月 2nd, 201323:23

    同赞!非常全面的总结。使用mfence来实现happen-before比使用lock的性能要好吧?中间提到foo函数里面的memory write reordering, foo执行完后 A=1, B=0, 但是因为进入foo之前A,B的值可能已经不是初值,第一次看到这里还想了一下为什么一定会是1和0. 这里的reordering主要也就是把读和写隔开,消除相邻指令的data dependence, 选gcc是因为VC++不会做这个优化吗?

    • hedengcheng
      12月 3rd, 201308:26

      vc,我没做这个测试,因为原来这个用例已经有了,我就直接在gcc下做了钱。

    • Janine
      4月 21st, 201607:35

      Wow! Talk about a posting kncikong my socks off!

  5. hiproz
    12月 3rd, 201301:06

    清晰 明了,一气呵成。特支持一下,期待更多精良大作。

  6. xiaoyang
    12月 3rd, 201308:10

    好文章,支持,以前觉得自己入门了,现在发现还很远,最起码的几个名词都没听说过。

  7. 东青
    12月 3rd, 201309:46

    很不错,尤其是最后一段从volatile关键字的起源说起,让我们知道了他的来龙去脉,更好的加深了理解。

  8. victor
    12月 3rd, 201309:53

    lz你好,第一个例子我用gcc -S t.c 编译为什么观察不到你提到的那种变化,这个与你的那个fn()函数没关系吧,想知道为什么

    • hedengcheng
      12月 3rd, 201311:09

      第一个例子,我用的是vc编译器,gcc没试过,因此尚不知道原因。

      • Adam
        12月 9th, 201317:44

        是不是用-S只是进行了汇编,没有经过优化阶段,应该对编译完生成的.o文件,再反汇编看。

    • hedengcheng
      12月 3rd, 201311:10

      跟fn函数是有关的,我可以把这个test完整代码贴出来。

  9. tengfei
    12月 3rd, 201312:46

    好文章啊,受益了。赞。

  10. catmonkeyxu
    12月 3rd, 201314:01

    受教了。那么能够实现happens-before语义性能最快的方式是什么呢?似乎就是lock指令?

    • hedengcheng
      12月 3rd, 201318:41

      嗯,其实个人感觉性能真没有那么明显的差别,更大的差别,还是在于对和错。

  11. shunruo
    12月 3rd, 201321:56

    VC中,volatile 是带Acquire 和 release 语义的。

  12. colin陶
    12月 5th, 201313:26

    太赞了…菜鸟路过…^.^学习学习,转走了哈,大牛

  13. zym
    12月 13th, 201321:50

    对于测试用例五,测试了一下gcc 4.7.2也是如此。准确的说应该是下面这个顺序:
    1. A=B
    2.B=0
    3.A=A+1
    可见B为0时A还没有加1!但我个人认为这里面的gcc的优化是有严重问题的,甚至可以当作是bug了。

    同时测试了clang 3.3,即使在-O2下也绝对不会把B=0优化到A=B+1前面。

    • hedengcheng
      12月 15th, 201317:17

      这个不算bug。编译器只要保证你程序达到的效果不变,中间的执行过程,是可以任意优化的。你可以试试,一段简单的代码,分别用debug,o1-3 release编译。debug,o1可能还跟代码基本一致,但是,o2-3跟代码的顺序,差异就会很大了。

    • Wizmann
      4月 24th, 201409:17

      c++不知道有多线程这个东西,所以只保证单线程下运行的正确。(这个问题应该在C++11得到了改变

  14. 邓波
    12月 18th, 201316:29

    多线程访问共享数据如果涉及写操作当然要加锁。这不是明摆着么?呵呵。

  15. fusijie
    12月 21st, 201308:32

    赞,令人十分享受的文章。

  16. leo
    2月 13th, 201406:41

    对x86而言,貌似那个volatile例子不太适合,几乎不太可能出现你说的问题。
    x86下,store之间不会有乱序出现,所以不会出现 flag=true then something=1。

    • hedengcheng
      2月 13th, 201412:47

      嗯,x86不会出现storestore乱序

      • 阳凌
        4月 2nd, 201512:00

        大侠,我想问一下,x86 不是还有 oostore架构吗? 那如果是x86 oostore架构会不会出现,flag=true then something = 1;的情况呢? google 了x86 oostore的资料,几乎没有,能介绍一下吗》

        • hedengcheng
          4月 2nd, 201515:17

          这个我不清楚,oostore能给点参考资料吗?

          • 阳凌
            4月 2nd, 201517:16

            我也看你给的下面,是截取自Wiki上的一幅图,列举了不同CPU架构,可能存在的指令乱序。wiki上的一个图。
            里面提到了 x86 oostore 和X86 还有IA64单独分开的。,应该是不同的架构,从wiki给的资料来看,似乎IA64和oostore的乱法是一样的乱法。

  17. clr
    3月 15th, 201422:24

    关于顺序性,我不确定你说的“CPU乱序执行”会影响volatile 变量之间的执行顺序。正如你后面所说的,volatile一开始是由MMIO引入,所以,不管是CPU还是编译器都应该能够确保volatile变量间执行的顺序性,否则,往某一个端口的输出或者读取就会仍然无法得到保证。

  18. 莫铭
    3月 22nd, 201413:17

    接触到很多东西,以前模糊不明的也稍清晰点了,非常感谢,期待更多的好文章。

  19. aCayF
    5月 18th, 201420:11

    提一个疑惑,还望前辈能帮忙解答一下,前面所说的volatile关键字会强制将数据写到内存中去,这里强制的意思是就算cache用了write-back的方式,在这次操作中也会保证被强制写到内存而不只写到cache为止吗?

    • hedengcheng
      5月 21st, 201418:41

      如果你将cache当作内存的一部分,就可以理解了。所谓的强制写内存,读内存,是相对于register而言的。

  20. apprentice89
    5月 21st, 201410:03

    清晰易懂,非常感谢!

  21. OWR
    7月 1st, 201411:55

    Very Good!
    Thank you

  22. jery
    11月 11th, 201417:07

    非常感谢,阅读本文之后让我对volatile有了清晰、完整的理解。

  23. 周大侠
    12月 25th, 201410:11

    =。= 我之前连volatile都不加的、、、、看来会出大问题啊、

  24. yyy
    10月 22nd, 201500:44

    1,“指令乱序执行的行为:StoreLoad”这个说法我认为是错误的

    cpu指令乱序和StoreLoad是两个概念,linux内核文档memeory barrier有很清晰的描述。我曾经在wiki上也看到过把两者混为一谈的说法。

    2,你举得例子something,otherthing是个典型的内存屏障的例子,assert(something == 1)是不能保证的。这个例子和volatile没有必然关系。

    3,我认为在某些场景下volatile是可用的,在多线程下不涉及互相之间有前后关系的多个内存信息的访问,那么volatile是可以用来做为线程间共享标识的。

    4,微软msdn中有提到c++ 11 ISO Standard 之后是不要用volatile做线程间通信的,之前是可以得。而且对于使用了 /volatile:ms 编译开关,volatile的代码也是可以使用在多线程环境下的。

  25. increase app store ranking
    12月 11th, 201514:16

    Want to Boost your Visibility on the App Store? It should be noted ios app store optimization that keyword changes, name, screeshots, video, ratings and reviews. If you are looking for the top aso services to improve your app ranking and boost app downloads, then you just have found the perfect site,ios app store optimization

  26. Max
    2月 16th, 201619:49

    cool!花了很多心思!

  27. Joan
    4月 21st, 201605:00

    This “free sharing” of inotimaorfn seems too good to be true. Like communism.

  28. 咕咕叫
    6月 14th, 201610:50

    Wizmann :
    c++不知道有多线程这个东西,所以只保证单线程下运行的正确。(这个问题应该在C++11得到了改变

    我也想知道博主对于不同线程的资源对象的访问,是如何保证顺序性的。。。

    Volatile:顺序性
    此处的例子,多线程的资源访问难道靠自己的逻辑或者肉眼去看么?
    是我孤陋寡闻还是