1. Amdahl定律
一个很简单的量化公式,用来计算一个程序中串行部分所占多少对程序加速比的影响或者用来计算计算机硬件配置中某个设备的速度提高能够将整个系统的速度提高多少。
假设一个串行程序执行的总时间为1,不能被并行化的部分占的时间比例为p,即串行化的部分为p,可并行化的时间为:1-p。如果用n个核用来加速的话,加速比为:
如果一个程序中只有50%部分能够被并行化,那么即使使用100个核,能达到的最好的加速比为1.98,即不会达到2.除非n无穷大。
Amdahl就像克莱姆准则一样不能用于实际中,因为很难精确的使用这个公式。我们需要找到程序中所有的串行部分和并行部分。串行部分不仅仅存在于代码中必需的依赖关系,可能还有底层库函数中的串行,还有操作系统的串行部分,甚至还有硬件本身的串行部分。
2. 异步应用程序的实现方法
常见的实现方式有:多线程+同步,异步I/O,非阻塞I/O,信号,事件。异步I/O和非阻塞I/O并不是一回事,当然在有些场合下也会混淆使用。一般来说,异步侧重于表达并行,两个执行流可以同时进行。非阻塞一般还包含一个查询的过程,因为要查询所要进行的操作是否完成。
异步I/O使用起来难度比多线程大,理解和维护起来的难度一般也比多线程麻烦。同样使用信号也是这个缺点,因为单个线程可以使用同步的方式,线程之间可以使用信号来通信。这样就好像一个人同时做很多件事情和一个团队来协作来完成这些事情的区别一样。后者的执行逻辑会更加清晰,而且多线程+同步的方式能够实现所有异步I/O带来的优势。
使用UNIX信号的方式有严重的局限性,因为收到信号后所有的逻辑放在一个信号处理函数中执行,这并不是信号处理函数设计的初衷。而且,使用信号会显得程序的组织结构比较混乱,维护起来比较麻烦。
事件流也是实现异步应用程序的一种方式。一般系统底层会将应用程序收到的各种事件缓冲到一个队列中,然后又底层负责将各种事件对应到一个回调函数上,串行的处理各个事件,即调用各个回调函数。这种方式的最大缺点就是有些事件的回调函数的执行事件比较长,会导致其他事件不能得到很好地相应。在Windows系统的.NET架构上,事件+回调函数是使用的比较多的一种方式。事件流的缺点在于其处理的顺序性。当然事件流+回调函数+多线程是一种不错的选择。
3. 编程模型
各个编程模型的计算能力是等价的,但是对问题抽象层次是不同的。汇编语言在表示程序的结构方面显得比较笨拙,但是使用C语言就将程序的设计结构显式的表现出来了。使用C语言进行数据封装和多态处理显得很笨拙,使用C++就能很好地处理这个问题。使用关系数据库在解决大规模数据问题上显得很笨拙,使用Map-Reduce就能很好的处理某些问题。非线程代码不能显式的表达操作的同步性,使用线程代码就能很好的表达出这些同步性。线程源代码让独立的模块或者松耦合的模块更加清楚的表现出来。
不同的编程模型对应着不同的抽象层次,对于问题域的抽象恰到好处的抽象模型有助于我们解决更复杂的问题。
4. 什么情况下使用线程
使用线程是有代价的,如果程序是计算密集型且每一步都有依赖,那么使用线程反而会导致效率下降,因为多个线程之间需要切换,还要负责维护锁,信号量等设施。对于可并行的计算密集型问题和I/O与计算可重叠的问题使用多线程一般会达到显著的效果。程序中有多个可并发的模块的时候,使用线程也能提高程序的响应速度。