《C++ 并发编程》- 第1章 你好,C++的并发世界

《C++ 并发编程》- 第1章 你好,C++的并发世界

 

转载自并发编程网 – ifeve.comios

 

Snip20131231_6
本文是《C++ 并发编程》的第一章,感谢人民邮电出版社受权并发编程网发表此文,版权全部,请勿转载。该书将于近期上市。程序员

本章主要内容web

  • 何谓并发和多线程
  •  为何要在应用程序中使用并发和多线程
  •  C++并发支持的发展历程
  •  一个简单的C++多线程程序是什么样的

这是C++用户的振奋时刻。距1998年初始的C++标准发布13年后,C++标准委员会给予程序语言和它的支持库一次重大的变革。新的C++标准(也被称为C++11或C++0x)于2011年发布并带来了不少的改变,使得C++的应用更加容易并富有成效。算法

 

在C++11标准中一个最重要的新特性就是支持多线程程序。这是C++标准第一次在语言中认可多线程应用的存在,并在库中为编写多线程应用程序提供组件。这将使得在不依赖平台相关扩展下编写多线程C++程序成为可能,从而容许以有保证的行为来编写可移植的多线程代码。这也恰逢程序员更多地寻求广泛的并发,特别是多线程程序,来提升应用程序的性能。编程

这本书讲述的就是在C++编程中对多线程并发的使用,以及令其成为可能的C++语言特性和库工具。我会以解释并发和多线程的含义以及为何要在应用程序中使用并发开始。在快速绕行阐述为何在应用程序中会不使用并发以后,我会对C++中并发支持进行概述,并以一个简单的C++并发实例结束这一章。具备开发多线程应用程序经验的读者能够跳过前面的小节。在随后几章会将涵盖更多普遍的例子,而且更深刻地了解库工具。本书最后附有多线程与并发所有的C++标准库工具的深刻参考。浏览器

那么,什么是并发(concurrency多线程(multithreading缓存

1.1 什么是并发

在最简单和最基本的层面,并发是指两个或更多独立的活动同时发生。并发在生活中随处可见;咱们能够一边走路一边说话,也能够两只手同时做不一样的动做,还有咱们每一个人都相互独立地过咱们的生活——我在游泳的时候你能够看球赛,等等。安全

1.1.1 计算机系统中的并发

当咱们提到计算机术语的并发,咱们指的是在单个系统里同时执行多个独立的活动,而不是顺序地或是一个接一个地。这并非新现象,多任务操做系统经过任务切换容许一台计算机在同一时间运行多个应用程序已司空见惯多年,一些高端的多处理器服务器启用真正的并发的时间更为长久。真正有新意的是增长计算机真正并行运行多任务的广泛性,而不仅是给人这种错觉。服务器

之前,大多数计算机都有一个处理器,具备单个处理单元或核心,至今许多台式机器还是这样。这种计算机在某一时刻只能够真正执行一个任务,但它能够每秒切换任务许屡次。经过作一点这个任务而后再作一点别的任务,看起来像是任务在并行发生。这就是任务切换(task switching。咱们仍然将这样的系统论为并发(concurrency,由于任务切换得太快,以致于没法分辨任务在什么时候会被暂挂而切换到另外一个任务。任务切换给用户和应用程序自己提供了一种并发的假象。因为这只是并发的假象,当应用程序执行在单处理器任务切换环境下,与执行在真正的并发环境下相比,其行为仍是有着微妙的不一样。特别地,对内存模型不正确的假设(详见第5章)在这样的环境中可能不会出现。这将在第10章中做深刻讨论。网络

包含多个处理器的计算机用于服务器和高性能计算任务已有多年,如今基于单个芯片上具备多于一个核心的处理器(多核心处理器)的计算机也成为愈来愈常见的台式机器。不管它们拥有多个处理器或一个多核处理器(或二者兼具),这些计算机可以真正的并行运行超过一个任务。咱们称之为硬件并发(hardware concurrency

图1.1显示了一个计算机处理刚好两个任务时的理想情景,每一个任务被分为10个相等大小的块。在一个双核机器(具备两个处理核心)中,每一个任务能够在各自的核心执行。在单核机器上作任务切换时,每一个任务的块交织进行。但它们也隔开了一位(图中所示灰色分隔条的厚度大于双核机器的分隔条);为了实现交织进行,该系统每次从一个任务切换到另外一个时都得执行一次上下文切换(context switch,而这是须要时间的。为了执行上下文切换,操做系统必须得为当前运行的任务保存CPU的状态和指令指针,算出要切换到哪一个任务,并为要切换到的任务从新加载处理器状态。而后CPU可能要将新任务的指令和数据的内存载入到缓存中,这可能阻止CPU执行任何指令,形成的进一步延迟。

图 1.1并发的两种方式:双核机器的并行执行对比单核机器的任务切换

图 1.1并发的两种方式:双核机器的并行执行对比单核机器的任务切换

尽管硬件并发的可用性在多处理器或多核系统上更显著,有些处理器却能够在一个核心上执行多个线程。要考虑的最重要的因素是硬件线程(hardware threads的数量:即硬件能够真正并发运行多少独立的任务。即使是具备真正硬件并发的系统,也很容易有超过硬件可并行运行的任务要执行,因此在这些状况下任务切换仍将被使用。例如,在一个典型的台式计算机上可能会有几百个的任务在运行,执行后台操做,即便在计算机名义上是空闲的。正是任务切换使得这些后台任务能够运行,并使得你能够同时运行文字处理器、编译器、编辑器和web浏览器(或任何应用的组合)。图1.2显示了四个任务在一台双核机器上的任务切换,仍然是将任务整齐地划分为同等大小块的理想状况。实际上,许多因素会使得分割不均和调度不规则。这些因素中的一部分将涵盖在第8章中,那时咱们来看一看影响并行代码性能的因素。

全部的技术、功能和本书所涉及的类均可以被使用,不管你的应用程序是在单核处理器或多核处理器上运行,也无论是任务切换或是真正的硬件并发。但你能够想象,如何在你的应用程序中使用并发将很大程度上取决于可用的硬件并发。这将在第8章中涵盖,在那里咱们具体研究C++代码并行设计问题。

图 1.2四个任务在两个核心之间的切换

图 1.2四个任务在两个核心之间的切换

1.1.2 并发的途径

想象一下两个程序员一块儿作一个软件项目。若是你的开发人员在独立的办公室,它们能够各自平静地工做,而不会互相干扰,而且他们各有本身的一套参考手册。然而,沟通起来就不那么直接了;不能转身而后互相交谈,他们必须用电话、电子邮件或走到对方的办公室。同时,你须要掌控两个办公室的开销,还要购买多份参考手册。

如今想象一下把开发人员移到同一间办公室。他们如今能够地相互交谈来讨论应用程序的设计,他们也能够很容易的用纸或白板来绘制图表,辅助阐释设计思路。你如今只有一个办公室要管理,只要一组资源就能够知足。消极的一面是,他们可能会发现难以集中注意力,而且还可能存在资源共享的问题(“参考手册跑哪去了?”)

组织开发人员的这两种方法表明着并发的两种基本途径。每一个开发人员表明一个线程,每一个办公室表明一个处理器。第一种途径是有多个单线程的进程,这就相似让每一个开发人员在他们本身的办公室,而第二种途径是在单一进程里有多个线程,这就相似在同一个办公室里有两个开发人员。你能够随意进行组合,而且拥有多个进程,其中一些是多线程的,一些是单线程的,但原理是同样的。让咱们在一个应用程序中简要地看一看这两种途径。

多进程并发

在一个应用程序中使用并发的第一种方法,是将应用程序分为多个、独立的、单线程的进程,它们运行在同一时刻,就像你能够同时进行网页浏览和文字处理。这些独立的进程能够经过全部的常规的进程间通讯渠道互相传递消讯息(信号、套接字、文件、管道等等),如图1.3所示。有一个缺点是这种进程之间的通讯一般设置复杂,或是速度较慢,或二者兼备,由于操做系统一般在进程间提供了大量的保护,以免一个进程不当心修改了属于另外一个进程的数据。另外一个缺点是运行多个进程所需的固有的开销:启动进程须要时间,操做系统必须投入内部资源来管理进程,等等。

固然,也并不全是缺点:操做系统在线程间提供的附加保护操做和更高级别的通讯机制,意味着能够比线程更容易地编写安全的并发代码。事实上,相似于为Erlang编程语言提供的环境,使用进程做为重大做用并发的基本构造快。

使用独立的进程实现并发还有一个额外的优点——你能够在经过网络链接的不一样的机器上运行的独立的进程。虽然这增长了通讯成本,但在一个精心设计的系统上,它多是一个提升并行可用行和提升性能的低成本方法。

图 1.3一对并发运行的进程之间的通讯

图 1.3一对并发运行的进程之间的通讯

多线程并发

并发的另外一个途径是在单个进程中运行多个线程。线程很像轻量级的进程:每一个线程相互独立运行,且每一个线程能够运行不一样的指令序列。但进程中的全部线程都共享相同的地址空间,而且从全部线程中访问到大部分数据——全局变量仍然是全局的,指针、对象的引用或数据能够在线程之间传递。虽然一般能够在进程之间共享内存,但这难以创建而且一般难以管理,由于同一数据的内存地址在不一样的进程中也不尽相同。图1.4显示了一个进程中的两个线程经过共享内存进行通讯。

图 1.4同一进程中的一对并发运行的线程之间的通讯

图 1.4同一进程中的一对并发运行的线程之间的通讯

共享的地址空间,以及缺乏线程间的数据保护,使得使用多线程相关的开销远小于使用多个进程,由于操做系统有更少的簿记要作。可是,共享内存的灵活性是有代价的:若是数据要被多个线程访问,那么程序员必须确保当每一个线程访问时所看到的数据是一致的。线程间数据共享可能会遇到的问题、所使用的工具以及为了不问题而要遵循的指导方针在本书中都有涉及,特别是在第三、四、5和8章中。这些问题并不是不可克服,只要在编写代码时适当地注意便可,但这却意味着必须对线程之间的通讯做大量的思考。

相比于启动多个单线程进程并在其间进行通讯,启动单一进程中的多线程并在其间进行通讯的开销更低,这意味着若不考虑共享内存可能会带来的潜在问题,它是包括C++在内的主流语言更青睐的并发途径。此外,C++标准没有为进程间通讯提供任何原生支持,因此使用多进程的应用程序将不得不依赖平台相关的API来实现。所以,本书专门关注使用多线程的并发,而且以后提到并发均是假定经过使用多线程来实现的。

明确了什么是并发后,如今让咱们来看看为何要在应用程序中使用并发。

1.2 为何使用并发?

在应用程序中使用并发的缘由主要有两个:关注点分离和性能。事实上,我甚至能够说它们差很少是使用并发的惟一缘由;当你观察的足够仔细时,一切其余因素均可以归结到这二者之一(或者多是两者兼有,固然,除了像“由于我愿意”这样的缘由以外)。

1.2.1 为了关注点分离而使用并发

在编写软件时,关注点分离几乎老是个好主意;经过将相关的代码放在一块儿并将无关的代码分开,可使你的程序更容易理解和测试,从而减小出错的可能性。你可使用并发来分隔不一样的功能区域,即便在这些不一样功能区域的操做须要在同一时刻发生的穷况下;若不显式地使用并发,你要么被迫编写任务切换框架,要么在操做中主动地调用不相关的一段代码。

考虑一类带有用户界面的密集处理型应用程序,例如为台式计算机提供的DVD播放程序。这样一个应用程序基本上具有两套职能:它不只要从光盘中读取数据,解码图像和声音,并把它们及时输出至视频和音频硬件,从而实现DVD的无错播放;它还要接受来自用户的输入,例如当用户单击暂停或返回菜单甚至退出按键的时候。在单个线程中,应用程序须在回放期间按期检查用户的输入,因而将将DVD回放代码和用户界面代码合在一块儿。经过使用多线程来分隔这些关注点,用户界面代码和DVD回放代码再也不须要如此紧密的交织在一块儿;一个线程能够处理用户界面,另外一个处理DVD回放。它们之间会有交互,例如用户点击暂停,但如今这些交互直接与眼前的任务有关。

这会带来响应性的错觉,由于用户界面线程一般能够当即响应用户的请求,即便在请求被传达给干活的线程时,响应为简单地显示正忙的光标或请等待的消息。相似地,独立的线程常被用于运行必须在后台连续运行的任务,例如在桌面搜索程序中监视文件系统的变化。以这种方式使用线程通常会使每一个线程的逻辑更加简单,由于它们之间的交互能够被限制为清晰可辨的点,而不是处处散播不一样任务的逻辑。

在这种状况下,线程的数量与CPU可用内核的数量无关,由于对线程的划分是基于概念上的设计而不是试图增长吞吐量。

1.2.2 为了性能而使用并发

多处理器系统已经存在了几十年,但直到最近,他们几乎只能在超级计算机、大型机和大型服务器系统中才能看到。然而芯片制造商愈来愈倾向于多核芯片的设计,即在单个芯片上集成二、四、16或更多的处理器,从而达到比单核心更好的性能。所以,多核台式计算机,甚至多核嵌入式设备,如今愈来愈广泛。这些计算机的计算能力的提升不是源自使单一任务运行的更快,而是源自并行运行多个任务。在过去,程序员曾坐看他们的程序随着处理器的更新换代而变得更快,无需他们这边作出任何努力。可是如今,就像Herb Sutter所说的,“免费的午饭结束了。”[1]若是软件想要利用日益增加的计算能力,它必须设计为并发运行多个任务。程序员所以必须留意,并且那些迄今都忽略并发的人们在必须注意它并将其加入他们的工具箱中。

有两种方式为了性能使用并发。首先,也是最明显的,是将一个单个任务分红几部分且各自并行运行,从而下降总运行时间。这就是任务并行(task parallelism。虽然这听起来很直观,但它能够是一个至关复杂的过程,由于在各个部分之间可能存在不少的依赖。区别多是在过程方面——一个线程执行算法的一部分而另外一个线程执行算法的另外一个部分——或是在数据方面——每一个线程在不一样的数据部分上执行相同的操做。后一种方法被称为数据并行(data parallelism

容易受这种并行影响的算法常被称为易并行(embarrassingly parallel。抛开你可能会尴尬地面对很容易并行化的代码这一含义,这是一件好事情:我曾遇到过的关于此算法的别的术语是天然并行(naturally parallel便利并发(conveniently concurrent。易并行算法具备良好的可扩展特性——随着可用硬件线程数量的提高,算法的并行性能够随之增长与之匹配。这样的一个算法是谚语“人多力量大”的完美体现。对于非易并行算法的那一部分,你能够将算法划分为一个固定(于是不可扩展)数量的并行任务。在线程之间划分任务的技巧涵盖在第8章中。

使用并发来提高性能的第二种方法是使用可用的并行方式来解决更大的问题;与其同时处理一个文件,不如酌情处理2个或10个或20个。虽然这实际上只是数据并行的一种应用,经过对多组数据同时执行相同的操做,但仍是有不一样的重点。处理一个数据块仍然须要一样的时间,但在相同的时间内却能够处理更多的数据。固然,这种方法也存在限制,且并不是在全部状况下都是有益的,可是这种方法所带来的吞吐量提高可让一些新玩意变得可能,例如,若是图片的各部分能够并行处理,就能提升视频处理的分辨率。

1.2.3 何时不使用并发

知道什么时候不使用并发与知道什么时候使用它同样重要。基本上,不使用并发的惟一缘由就是在收益比不上成本的时候。使用并发的代码在不少状况下难以理解,所以编写和维护的多线程代码就有直接的脑力成本,同时额外的复杂性也可能致使更多的错误。除非潜在的性能增益足够大或关注点分离地足够清晰,能抵消确保其正确所需的额外的开发时间以及与维护多线程代码相关的额外成本,不然不要使用并发。

一样地,性能增益可能不会如预期的那么大;在启动线程时存在固有的开销,由于操做系统必须分配相关的内核资源和堆栈空间,而后将新线程加入调度器中,全部这一切都占用时间。若是在线程上运行的任务完成得很快,那么任务实际上占据的时间与启动线程的开销时间相比显得微不足道,可能会致使应用程序的总体性能还不如经过产生线程直接执行该任务。

此外,线程是有限的资源。若是让太多的线程同时运行,则会消耗操做系统资源,而且使得操做系统总体上运行得更缓慢。不只如此,运行太多的线程会耗尽进程的可用内存或地址空间,由于每一个线程都须要一个独立的堆栈空间。对于一个可用地址空间限制为4GB的扁平架构的32位进程来讲,这尤为是个问题:若是每一个线程都有一个1MB的堆栈(对于不少系统来讲是典型的),那么4096个线程将会用尽全部地址空间,再也不为代码、静态数据或者堆数据留有空间。虽然64位(或者更大)的系统不存在这种直接的地址空间限制,它们仍然只具有有限的资源:若是你运行太多的线程,最终会致使问题。尽管线程池(参见第9章)能够用来限制线程的数量,但这并非灵丹妙药,它们也有它们本身的问题。

若是客户端/服务器应用程序的服务器端为每个连接启动一个独立的线程,对于少许的连接是能够正常工做的,但当一样的技术用于须要处理大量连接的高需求服务器时,就会由于启动太多线程而迅速耗尽系统资源。在这种场景下,谨慎地使用线程池能够提供优化的性能(参见第9章)。

最后,运行越多的线程,操做系统就须要作越多的上下文切换。每一个上下文切换都须要耗费本能够花在有价值工做上的时间,因此在某些时候,增长一个额外的线程实际上会下降而不是提升应用程序的总体性能。为此,若是你试图获得系统的最佳性能,考虑可用的硬件并发(或缺少之)并调整运行线程的数量是必需的。

为了性能而使用并发就像全部其余优化策略同样:它拥有极大提升应用程序性能的潜力,但它也可能使代码复杂化,使其更难理解和更容易出错。所以,只有对应用程序中的那些具备显著增益潜力的性能关键部分才值得这样作。固然,若是性能收益的潜力仅次于设计清晰或关注点分离,可能也值得使用多线程设计。

假设你已经决定确实要在应用程序中使用并发,不管是为了性能、关注点分离,或是由于“多线程星期一”,对于C++程序员来讲意味着什么?

1.3 在C++中使用并发和多线程

经过多线程为并发提供标准化的支持对C++来讲是新鲜事物。只有在即将到来的C++11标准中,你才能不依赖平台相关的扩展来编写多线程代码。为了理解新版本C++线程库中众多规则背后的基本原理,了解其历史是很重要的。

1.3.1 C++多线程历程

1998 C++标准版不认可线程的存在,而且各类语言要素的操做效果都以顺序抽象机的形式编写。不只如此,内存模型也没有被正式定义,因此对于1998 C++标准,你没办法在缺乏编译器相关扩展的状况下编写多线程应用程序。

固然,编译器供应商能够自由地向语言添加扩展,而且针对多线程的C API的流行——例如在POSIX C和Microsoft Windows API中的那些——致使不少C++编译器供应商经过各类平台相关的扩展来支持多线程。这种编译器支持广泛地受限于只容许使用该平台相应的C API以及确保该C++运行时库(例如异常处理机制的代码)在多线程存在的状况下运行。尽管极少有编译器供应商提供了一个正式的多线程感知内存模型,但编译器和处理器的实际表现也已经足够好,以致于大量的多线程的C++程序已被编写出来。

因为不知足于使用平台相关的C API来处理多线程,C++程序员曾指望他们的类库提供面向对象的多线程工具。像MFC这样的应用程序框架,以及像Boost和ACE这样的C++通用C++类库曾积累了多套C++类,封装了下层的平台相关API并提供高级的多线程工具以简化任务。各种库的具体细节,特别是在启动新线程的方面,存在很大差别,可是这些类的整体构造存在不少共通之处。有一个为许多C++类库共有的,同时也是为程序员提供很大便利的特别重要的设计,就是带锁的资源得到即初始化(RAII, Resource Acquisition Is Initialization的习惯用法,来确保当退出相关做用域的时候互斥元被解锁。

许多状况下,现有的C++编译器所提供的多线程支持,例如Boost和ACE,综合了平台相关API以及平台无关类库的可用性,为编写多线程C++代码提供一个坚实的基础,也所以大约有数百万行C++代码做为多线程应用程序的一部分而被编写出来。但缺少标准的支持,意味着存在缺乏线程感知内存模型从而致使问题的场合,特别是对于那些试图经过使用处理器硬件能力来获取更高性能,或是编写跨平台代码可是在不一样平台之间编译器的实际表现存在差别的状况。

1.3.2 新标准中的并发支持

全部这些都随着新的C++11标准的发布而改变了。不只有了一个全新的线程感知内存模型,C++标准库也被扩展了,包含了用于管理线程(参见第2章)、保护共享数据(参见第3章)、线程间同步操做(参见第4章)以及低级原子操做(参见第5章)的各个类。

新的C++线程库很大程度上基于以前经过使用上文提到的C++类库而积累的经验。特别地,Boost线程库被用做新类库所基于的主要模型,不少类与Boost中的对应者共享命名和结构。在新标准演进的过程当中,这是个双向流动,Boost线程库也改变了本身,以便在多个方面匹配C++标准,所以从Boost迁移过来的用户将会发现本身很是习惯。

正如本章开篇提到的那样,对并发的支持仅仅是新C++标准的变化之一,此外还存在不少对于编程语言自身的改善,可使得程序员们的工做更便捷。这些内容虽然不在本书的论述范围以内,可是其中的一些变化对于线程库自己及其使用方式已经造成了直接的冲击。附录A对这些语言特性作了简要的介绍。

C++中对原子操做的直接支持,容许程序员编写具备肯定语义的高效代码,而无需平台相关的汇编语言。这对于那些试图编写高效的、可移植代码的程序员们来讲是一个真正的福利;不只有编译器能够搞定平台的具体内容,还能够编写优化器来考虑操做的语义,从而让程序做为一个总体获得更好的优化。

1.3.3 C++线程库的效率

对于C++总体以及包含低级工具的C++类——特别是在新版C++线程库里的那些,参与高性能计算的开发者经常关注的一点就是效率。若是你正寻求极致的性能,那么理解与直接使用底层的低级工具相比,使用高级工具所带来的实现成本,是很重要的。这个成本就是抽象惩罚(abstraction penalty

C++标准委员会在总体设计C++标准库以及专门设计标准C++线程库的时候,就已经十分注重这一点了;其设计的目标之一就是在提供相同的工具时,经过直接使用低级API就几乎或彻底得不到任何好处。所以该类库被设计为在大部分主要平台上都能高效实现(带有很是低的抽象惩罚)。

C++标准委员会的另外一个目标,是确保C++能提供足够的低级工具给那些但愿与硬件工做得更紧密的程序员,以获取终极性能。为了达到这个目的,伴随着新的内存模型,出现了一个全面的原子操做库,用于直接控制单个位、字节、线程间同步以及全部变化的可见性。这些原子类型和相应的操做如今能够在不少地方加以使用,而这些地方之前一般被开发者选择下放到平台相关的汇编语言中。使用了新的标准类型和操做的代码于是具备更佳的可移植性,而且更易于维护。

C++标准库也提供了更高级别的抽象和工具,它们使得编写多线程代码更简单和不易出错。有时候运用这些工具确实会带来性能成本,由于必须执行额外的代码。可是这种性能成本并不必定意味着更高的抽象惩罚;整体来看,这种性能成本并不比经过手工编写等效的函数而招致的成本更高,同时编译器可能会很好地内联大部分额外的代码。

在某些状况下,高级工具提供超出特定使用需求的额外功能。在大部分状况下这都不是问题:你没有为你不使用的那部分买单。在罕见的状况下,这些未使用的功能会影响其余代码的性能。若是你更看重程序的性能,且代价太高,你可能最好是经过较低级别的工具来手工实现须要的功能。在绝大多数状况下,额外增长的复杂性和出错的概率远大于小小的性能提高带来的潜在收益。即便有证据确实代表瓶颈出如今C++标准库的工具中,这也可能归咎于低劣的应用程序设计而非低劣的类库实现。例如,若是过多的线程竞争一个互斥元,这将会显著影响性能。与其试图在互斥操做上刮掉一点点的时间,还不如从新构造应用程序以减小互斥元上的竞争来的划算。设计应用程序以减小竞争会在第8章中加以阐述。

在很是罕见的状况下,C++标准库不提供所需的性能或行为,这时则有必要运用使用平台相关的工具。

1.3.4 平台相关的工具

虽然C++线程库为多线程和并发处理提供了颇为全面的工具,可是在全部的平台上,都会有些额外的平台相关工具。为了能方便的访问那些工具而又不用放弃使用标准C++线程库带来的好处,C++线程库中的类型能够提供一个native_handle()成员函数,容许经过使用平台相关API直接操做底层实现。就其本质而言,任何使用native_handle()执行的操做是彻底依赖于平台的,这也超出了本书(同时也是标准C++库自己)的范围。

固然,在考虑使用平台相关的工具以前,明白标准库可以提供什么是很重要的,那么让咱们经过一个例子来开始。

1.4 开始入门

好,如今你有一个很棒的与C++11兼容的编译器。接下来呢?一个多线程C++程序是什么样子的?它看上去和其余全部C++程序同样,一般是变量、类以及函数的组合。惟一真正的区别在于某些函数能够并发运行,因此你须要确保共享数据的并发访问是安全的,详见第3章。固然,为了并发地运行函数,必须使用特定的函数以及对象来管理各个线程。

1.4.1 你好,并发世界

让咱们从一个经典的例子开始:一个打印“Hello World.”的程序。一个很是简单的在单线程中运行的Hello, World程序以下所示,当咱们谈到多线程时,它能够做为一个基准。

1 #include <iostream>
2 int main()
3 {
4     std::cout << "Hello World\n";
5 }

这个程序所作的一切就是将“Hello World”写进标准输出流。让咱们将它与下面清单所示的简单的Hello, Concurrent World程序作个比较,它启动了一个独立的线程来显示这个信息。

清单 1.1一个简单的Hello, Concurrent World程序

 1 #include <iostream>
 2 #include <thread>  //①
 3 void hello()  //②
 4 {
 5     std::cout << "Hello Concurrent World\n";
 6 }
 7 int main()
 8 {
 9     std::thread t(hello);  //③
10     t.join();  //④
11 }

第一个区别是增长了#include<thread>Œ①。在标准C++库中对多线程支持的声明在新的头文件中:用于管理线程的函数和类在<thread>中声明,而那些保护共享数据的函数和类在其余头文件中声明。

其次,写信息的代码被移动到了一个独立的函数中②。这是由于每一个线程都必须具备一个初始函数(initial function,新线程的执行在这里开始。对于应用程序来讲,初始线程是main(),可是对于全部其余线程,这在std::thread对象的构造函数中指定──在本例中,被命名为tŽ③的std::thread对象拥有新函数hello()做为其初始函数。

下一个区别:与直接写入标准输出或是从main()调用hello()不一样,该程序启动了一个全新的线程来实现,将线程数量一分为二──初始线程始于main()而新线程始于hello()

在新的线程启动以后Ž③,初始线程继续执行。若是它不等待新线程结束,它就将自顾自地继续运行到main()的结束,从而结束程序──有可能发生在新线程有机会运行以前。这就是为何在④这里调用join()的缘由──详见第2章,这会致使调用线程(在main()中)等待与std::thread对象相关联的线程,即这个例子中的t

若是这看起来像是仅仅为了将一条信息写入标准输出而作了大量的工做,那么它确实如此──正如上文1.2.3节所描述的,通常来讲并不值得为了如此简单的任务而使用多线程,尤为是若是在这期间初始线程无所事事。在本书后面的内容中,咱们将经过实例来展现在哪些情景下使用多线程能够得到明确的收益。

1.5 小结

在本章中,我说起了并发与多线程的含义以及在你的应用程序中为何你会选择使用(或不使用)它。我还说起了多线程在C++中的发展历程,从1998标准中彻底缺少支持,经历了各类平台相关的扩展,再到新的C++11标准中具备合适的多线程支持。该支持到来的正是时候,它使得程序员们能够利用随着新的CPU而带来的更增强大的硬件并发,由于芯片制造商选择了以多核心的形式使得更多任务能够同时执行的方式来增长处理能力,而不是增长单个核心的执行速度。

我在1.4节中的示例,来展现了C++标准库中的类和函数有多么的简单。在C++中,使用多线程自己并不复杂,复杂的是如何设计代码以实现其预期的行为。

在尝试了1.4节的示例以后,是时候看看更多实质性的内容了。在第2章中,咱们将看一看用于管理线程的类和函数。


[1] “The Free Lunch Is Over: A Fundamental Turn Toward Concurrency in Software,” Herb Sutter, Dr. Dobb’s

Journal, 30(3), March 2005. http://www.gotw.ca/publications/concurrency-ddj.htm.

原创文章,转载请注明: 转载自并发编程网 – ifeve.com

本文连接地址: 《C++ 并发编程》- 第1章 你好,C++的并发世界

相关文章
相关标签/搜索