为什么要并发编程
大型的软件项目常常包含非常多的任务需要处理。例如:对于大量数据的数据流处理,或者是包含复杂GUI界面的应用程序。如果将所有的任务都以串行的方式执行,则整个系统的效率将会非常低下,应用程序的用户体验会非常的差。另一方面,自上个世纪六七十年代英特尔创始人之一GordonMoo提出摩尔定义以来,CPU频率以每8个月翻一番的指数速度增长。但这一增长在最近的十年已经基本停滞,大家会发现曾经有过一段时间CPU的频率从G到达4G,但在这之后就停滞不前了。因此最近的新款CPU也基本上都是G左右的频率。相应的,CPU以更多核的形式在增长。目前的Intli7有8核的版本,Xon处理器达到了8核。并且,最近几年手机上使用的CPU也基本上是4核或者8核的了。由此,掌握并发编程技术,利用多处理器来提升软件项目的性能将是软件工程师的一项基本技能。本文以C++语言为例,讲解如何进行并发编程。并尽可能涉及C++,C++4以及C++7中的主要内容。并发与并行并发(Concurnt)与并行(Paralll)都是很常见的术语。Erlang之父JoArmstrong曾经以人们使用咖啡机的场景为例描述了这两个术语。如下图所示:并发:如果多个队列可以交替使用某台咖啡机,则这一行为就是并发的。并行:如果存在多台咖啡机可以被多个队列交替使用,则就是并行。这里队列中的每个人类比于计算机的任务,咖啡机类比于计算机处理器。因此:并发和并行都是在多任务的环境下的讨论。更严格的来说:如果一个系统支持多个动作同时存在,那么这个系统就是一个并发系统。如果这个系统还支持多个动作(物理时间上)同时执行,那么这个系统就是一个并行系统。你可能已经看出,“并行”其实是“并发”的子集。它们的区别在于是否具有多个处理器。如果存在多个处理器同时执行多个线程,就是并行。在不考虑处理器数量的情况下,我们统称之为“并发”。进程与线程进程与线程是操作系统的基本概念。无论是桌面系统:MacOS,Linux,Windows,还是移动操作系统:Android,iOS,都存在进程和线程的概念。进程(英语:procss),是指计算机中已运行的程序。进程为曾经是分时系统的基本运作单位。在面向进程设计的系统(如早期的UNIX,Linux.4及更早的版本)中,进程是程序的基本执行实体;线程(英语:thad)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。--维基百科关于这两个概念在任何一本操作系统书上都可以找到定义。网上也有很多文章对它们进行了解释。因此这里不再赘述,这里仅仅提及一下它们与编程的关系。对于绝大部分编程语言或者编程环境来说,我们所写的程序都会在一个进程中运行。一个进程至少会包含一个线程。这个线程我们通常称之为主线程。在默认的情况下,我们写的代码都是在进程的主线程中运行,除非开发者在程序中创建了新的线程。不同编程语言的线程环境会不一样,Java语言在很早就支持了多线程接口。(Java程序在Java虚拟机中运行,虚拟机通常还会包含自己特有的线程,例如垃圾回收线程。)。而对于JavaScript这样的语言来说,它就没有多线程的概念。当我们只有一个处理器时,所有的进程或线程会分时占用这个处理器。但如果系统中存在多个处理器时,则就可能有多个任务并行的运行在不同的处理器上。下面两幅图以不同颜色的矩形代表不同的任务(可能是进程,也可能是线程)来描述它们可能在处理器上执行的顺序。下图是单核处理器的情况:下面是四核处理器的情况:任务会在何时占有处理器,通常是由操作系统的调度策略决定的。在《Android系统上的进程管理:进程的调度》一文中,我们介绍过Linux的调度策略。当我们在开发跨平台的软件时,我们不应当对调度策略做任何假设,而应该抱有“系统可能以任意顺序来调度我的任务”这样的想法。并发系统的性能开发并发系统最主要的动机就是提升系统性能(事实上,这是以增加复杂度为代价的)。但我们需要知道,单纯的使用多线程并不一定能提升系统性能(当然,也并非线程越多系统的性能就越好)。从上面的两幅图我们就可以直观的感受到:线程(任务)的数量要根据具体的处理器数量来决定。假设只有一个处理器,那么划分太多线程可能会适得其反。因为很多时间都花在任务切换上了。因此,在设计并发系统之前,一方面我们需要做好对于硬件性能的了解,另一方面需要对我们的任务有足够的认识。关于这一点,你可能需要了解一下阿姆达尔定律了。对于这个定律,简单来说:我们想要预先意识到那些任务是可以并行的,那些是无法并行的。只有明确了任务的性质,才能有的放矢的进行优化。这个定律告诉了我们将系统并行之后性能收益的上限。关于阿姆达尔定律在Linux系统监测工具sysstat介绍一文中已经介绍过,因此这里不再赘述。C++与并发编程前面我们已经了解到,并非所有的语言都提供了多线程的环境。即便是C++语言,直到C++标准之前,也是没有多线程支持的。在这种情况下,Linux/Unix平台下的开发者通常会使用POSIXThads,Windows上的开发者也会有相应的接口。但很明显,这些API都只针对特定的操作系统平台,可移植性较差。如果要同时支持Linux和Windows系统,你可能要写两套代码。相较而言,Java自JDK.0就包含了多线程模型。这个状态在C++标准发布之后得到了改变。并且,在C++4和C++7标准中又对并发编程机制进行了增强。下图是最近几个版本的C++标准特性的线路图。编译器与C++标准编译器对于语言特性的支持是逐步完成的。想要使用特定的特性你需要相应版本的编译器。GCC对于C++特性的支持请参见这里:C++StandardsSupportinGCC。Clang对于C++特性的支持请参见这里:C++SupportinClang。下面两个表格列出了C++标准和相应编译器的版本对照:C++标准与相应的GCC版本要求如下:C++标准与相应的Clang版本要求如下:默认情况下编译器是以较低的标准来进行编译的,如果希望使用新的标准,你需要通过编译参数-std=c++xx告知编译器,例如:g++-std=c++7your_fil.cpp-oyour_program
测试环境本文的源码可以到下载我的github上获取,