当前位置导航:炫浪网>>网络学院>>编程开发>>C++教程>>C++进阶与实例

多线程内存模型

  这个系列其实早就想写了,断断续续关注C++0x也大约有两年余了,其间看着各个重要proposals一路review过来:rvalue-references、concepts、memory-model、variadic-templates、template-aliases、auto/decltype、GC、initializer-lists…
 
    总的来说C++09跟C++98相比的变化是极其重大的。这个变化体现在三个方面,一个是形式上的变化,即在编码形式层面的支持,也就是对应我们所谓的编程范式(paradigm)。C++09不会引入新的编程范式,但在对泛型编程(GP)这个范式的支持上会得到质的提高:concepts、variadic-templates、auto/decltype、template-aliases、initializer-lists皆属于这类特性。另一个是内在的变化,即并非代码组织表达方面的,memory-model、GC属于这一类。最后一个是既有形式又有内在的,r-value references属于这类。
 
    这个系列如果能够写下去,会陆续将C++09的新特性介绍出来。鉴于已经有许多牛人写了很多很好的tutor(这里,这里,还有C++标准主页上的一些introductive的proposals,如这里,此外C++社群中老当益壮的Lawrence Crowl也在google做了非常漂亮的talk)。所以我就不作重复劳动了:),我会尽量从一个宏观的层面,如特性引入的动机,特性引入过程中经历的修改,特性本身的最具代表性的使用场景,特性对编程范式的影响等方面进行介绍。至于细节,大家可以见每篇介绍末尾的延伸阅读。
 
 
动机
    内存模型是C++09最重大的特性之一,之所以重大是因为多线程并发编程将成为下一个十年的主题之一,对此C++小胡子Herb Sutter早有精彩的论述。
 
    为什么在C++里面要想顺畅地进行多线程编程需要对标准进行修订(而不仅仅是通过现有的多线程库如POSIX、boost.Thread即可)呢?对此Hans Boehm在他的著名的超级晦涩难懂的paper——《Threads Cannot be Implemented as a Library》——里面其实已经详尽地阐述了原因,但是,一,尽管这篇paper被到处cite,newsgroup上面关于到底能不能用volatile来实现线程安全性这类问题还是争议不断。这方面就连C++牛魔王Andrei Alexandrescu都犯过错误,可见有多难缠。二,这篇paper很难读,一般人就算头悬梁锥刺股一口气读上N遍,一转眼的工夫就又成丈二和尚了。Memory-model与多线程是一个非常棘手的领域。记得Andrei Alexandrescu曾在一篇专栏文章里面提到,大意是说,泛型编程难、编写异常安全的代码更难,但跟多线程编程比起来,它们就都成了娃娃吃奶。
 
    因此,要想比较透彻理解C++09内存模型的动机,光是Hans的那篇paper是不够的,C++09的内存模型沿袭的是Java的内存模型,Java社群在这个上面花了玩命的工夫,最后修订出来的标准的复杂度达到了不是给人看的地步(当然,只是其中的一个小部分,并非全部),Jeremy Manson在google做了一个关于java memory model的talk,也只是浅浅的从宏观层面谈了一下。所以既然Java社群已经花了这个工夫,而且C/C++/Java本就是同根生,所以也就乐得发扬一下拿来主义了,订阅了相关mailing-list的老大们会发现Java社群这方面的几个老大也时常在里面发言,语言无疆界啊:) [Page]
 
用一句话来说,修订C++的内存模型的原因在于:
 
现有的内存模型无法保证我们写出可移植的多线程程序
 
那为什么无法保证呢。对此许多人都用一句模棱两可令人摸不着头脑的话来解释:因为C++98中的内存模型是单线程的(虽然标准没有明确指出,但这是一个隐含结论)。这句话说了等于没说,让我想起那个关于数学家的笑话。人们难免要问,那为什么内存模型是单线程的就意味着无法写出可移植的多线程程序来呢?POSIX线程模型指导下不是存在了那么多的C/C++多线程程序吗?这又怎么解释呢?
 
所以,必得有一个最本质的解释,下面这个就是:
 
现有的单线程内存模型没有对编译器做足够的限制,从而许多我们看上去应该是安全的多线程程序,由于编译器不知道(并且根据现行标准(C++03)的单线程模型,编译器也无需关心)多线程的存在,从而可能做出不违反标准,但能够破坏程序正确性的优化(这类优化一旦导致错误便极难调试,基本属于非查看生成的汇编代码不可的那种)。
 
OK,说到现在,对什么是“内存模型”还没有解释呢。内存模型的正式定义很是飘逸,jsr133中这么说:
 
A memory model describes, given a program and an execution trace of that program, whether the execution trace is a legal execution of the program.
内存模型描述给定程序的某个特定的执行轨迹是否是该程序的一个合法执行。
 
其实这句话正常人多读几遍多少还能有有点似乎理解的感觉。接下来一句话就更诡异了:
 
For the Java programming language, the memory model works by examining each read in an execution trace and checking that the write observed by that read is valid according to certain rules.
对于Java来说,内存模型的工作模式如下:对一个给定执行轨迹上的每一读取操作(read),检查该读取操作所读到的对应的写操作结果(write)是否不违背一定的规则。
 
以上是内存模型的技术定义,其最大的缺点就是不能帮我们感性而直观的理解什么是内存模型。
 
目前的多线程编程模型从广义上来说一般不外乎共享内存模型(多个线程访问共享空间,通过加锁解锁和对全局变量的操作来进行交互)和消息传递模型这两种。其中共享内存模型目前仍然是主流中的主流(比如C家族语言用的就都是这一招),消息传递模型大家也都不陌生,目前最成熟的应用是在Erlang里面。当然,这样的分类是往大了说,往小了说可就麻烦了,可以参考这里。
 
本文要说的内存模型是针对共享内存下的多线程并发编程的。内存模型的技术定义刚才已经饶舌过了,其非技术定义是这样的:
 
一个内存模型对于语言的实现方回答这样一个问题:
哪些优化是被允许的(这里的“优化”其实僵硬地说应该是transformations,当然,由于这是我杜撰的非官方定义,所以就管不了那么多繁文缛节了。) 
 

共3页 首页 上一页 1 2 3 下一页 尾页 跳转到
相关内容
赞助商链接