并行中的正负两面
大多情况下,并行对复杂度影响过大,并间接导致测试困难---多线程或多进程导致的问题往往是有时发生,有时不发生,一般的测试手段并不足以发现这类问题。
所以原则上应该尽可能不用,除非收益足够大。或则说在满足需求的前提下,线程数和进程数应该尽可能少。
以多线程和多进程而论,确定“什么时候适合使用这种技术”是比“怎么使用这项技术”困难的多的事情。
现实中人们往往对事件,信号灯等同步处理手段关注过多,而对究竟应不应该启用多线程/进程关注的太少。这里来简单做个总结,对于下面这些场景,一般来讲启用多线程/进程是合适的:
- 数据很容易分割,处理不同数据时彼此间没有什么交互。比如说:你有10万个文件需要处理,每个文件的处理彼此独立。这时候显然要做并行,这样最终数据处理速度将依赖于并行分布的程度。这好像很简单,但却触发了MapReduce这样著名的模型(参见《软件随想录》)。在待处理数据持续增长的情形下,如果不启用并行,那几乎没有办法保证速度不会下降,这并不是单纯靠优化算法可以解决的问题。
- 监控设备响应(端口监视也应该属于这个类别)。这时候我们会希望能及时响应设备的事件,而不希望设备上的数据采集和对数据的处理彼此干涉。比如:Windows中就单独建立了Raw Input Thread(RIT)来监控整个系统的键盘和鼠标消息,而后再分发给各个进程中的线程。(详见《Windows核心编程》)。
- 优化UI响应速度。比如:用文档编辑器打开大文件时,如果不分离UI响应和实际处理,UI会挂起,很典型的应用是进度条的处理。使用了MVC(Model-View-Controller)模式的应用中大多时候可以适用这条,因为很多时候都需要View和Controller总是保持响应。
- 待处理的请求天生就是并行的。比如:当浏览不同网站时,不同的网站的显示会在不同的窗口中处理,这类处理天生就是并行的,既可以用多线程也可以用多进程。客户-服务器体系也是这类情形。
- 计算量很大,并且算法可以分割。有些传说中的领域(大气模拟、基因工程等)事实上是并行计算的主战场,比如在展示MPI(Message Passing Interface)的使用时,经常使用的计算π的例子,就是如此。但这离通用软件开发的距离就有点远,知之不详,这里就不列了。
这个列表应该进一步总结和加长,但限于精力,眼下却只能列这么多了。
对于多线程的误用,在MSDN上可以找到一份很好的总结,这里提取几个典型的结果:
- 锁争用和顺序执行。这时虽然启用了多线程,但实际执行起来这些线程是串行的。
- 过度订阅。如果系统的核数远少于线程数,同时启动很多个线程,时间往往会浪费在抢占上。
- I/O效率低。多线程被用来做频繁的I/O操作,这会导致很多时间开销在I/O操作上。
多进程的误用眼下看到的总结还不多,这里略过,有经验的可以帮忙一起总结。
最后加一点关于并行种类的补充资料:
为并行分类并不是很容易的事,这里只介绍一种最简单的,依据内存架构对并行进行分类的方法。依据内存架构,并行可以分为三类:
- Shared Memory: 常说的多线程即是这一类,比较有名的实现是OpenMP。
- Distributed Memory:如果多台PC组合起来进行并行计算那就是这一类。比较有名的实现是MPI。
- Hybrid Distributed-Shared Memory:如果多台PC进行分布,每台PC上还启用多核,那就是这类。
有人似乎还想进一步区分并行和并发,这就有点微妙了,个人不是很能分的清楚。
--------------------------------------------------------------
理想流 + 软件 = 《完美软件开发:方法与逻辑》
理想流 + 人生 = ??
理想流 + 管理 = ??
理想流 = 以概念和逻辑推演本质,追求真理。