一、多线程开发
多线程开发的特点就是并发(并行),早期的多线程说直白一些就为了更好的利用CPU,而后期的多线程就更广泛了,甚至CPU的增多,并行与并发已经不再有明显的界限。利用多线程进行并发的开发,是对程序员能力的一个基本的考验。虽然说在不同的平台,可能对多线程的处理有所不同,甚至没有多线程。但类似多线程这种并发或并行的机制大多是存在的。
特别是在Windows平台,线程开发更是无法绕开的,程序启动就会创建一个主线程。换句话说,在Windows平台上,多线程的使用是不可避免的。那么在设计者眼中,什么时候引入多线程设计,如何正确的使用多线程设计代码架构就是一个很现实的问题。
二、多线程设计的分析
多线程设计和开发的目的是什么?这才是设计和开发者的根本目标。设计者不能以一种复杂取代另外一种复杂,但可以用另外一种复杂来简化需要简化的一种复杂。多线程设计的目标包括:
- 提升性能
多线程的开发首要目标就是性能可以得到优化,如果不能提升性能,那其重要性必然会大大降低 - 提高资源利用率
多线程开发就是要发掘资源利用的最大可能,将各种资源应用特别是CPU的应用全部整合起来,实现最大的效率提升 - 提供更好的用户体验
这些用户体验,既包括交互上的方便快捷也包括响应反馈的速度,更包括对程序整体操作的流畅和稳定 - 解耦和逻辑简化
通过多线程对任务进行并发处理,可以将相关的任务操作流水化、独立化,并利用相关的算法将相关复杂的任务拆解成一个个容易实现的子任务或数据分场。通过复杂的多线程处理来解决耦合在一起复杂任务和数据处理逻辑
针对多线程这些目标,需要设计者对相关的具体的多线程的技术细节进行处理,包括:
- 尽量减少不必要的多线程交互
对于多线程间的通信,数据共享以及命令交互等,非必要一律不建议增加。必要的交互也可进行全面的控制,严格协调相关操作的机制。提前预防因交互产生的各种问题。 - 最小操作数据、最小任务单元及任务流、工作流的匹配
设计者要对线程可处理的最小的数据块、任务单元有着清晰的界定,并能够保证在正常的情况下,其工作流和任务流的匹配一致,减少甚至避免线程间的等待 - 设计异常和恢复机制
对于可预见的异常问题应该有控制手段,并可以实现线程任务的再次安全启动 - 深入理解和利用平台和语言的特性
在不同平台上,由于硬件支持的关系可能也会有所不同,比如CPU的核心数量、指令并行的支持程度、内存(如内存序)和缓存间的控制关系等等,都可能对多线程的开发有着不同的影响。同样,不同的系统平台也有着不同的情况需要处理。另外虽然主流以X64平台为主,但ARM和其它平台也在迅速的崛起,这个才是考验一个设计者的功力的时候。要针对不同的软硬件平台和系统以及特定或混合的语言开发都能设计出高效的多线程模型。
拿破化说过,没有不好的士兵,只有无能的将军。如何把多线程的内在影响与实际应用能条理的分析映射出来,才能体现出一个好的设计者的眼光。刀还是那把刀,但作战的结果大不为同
三、多线程的设计层面的应用
在实际的应用开发中,应该把握一个原则即能不使用多线程就不使用多线程进行开发。这句话说起来简单,但真正应用起来就比较麻烦了。稍微复杂的情况下可能都需要引入多线程的设计和开发。在这种情况下,那个原则就没有意义了。
从设计者的角度来看,多线程要区分用户态和内核态的多线程。当然,在某些情况下,二者是可以进行相互映射的。虽然对于大多数情况下讲,用户态的多线程是一种大概率事件,但仍然在头脑中保持着一个完整的设计链条。
在用户态的多线程设计中,如何将功能应用展开为可串行和可并发的任务,是设计者最基本的一个能力。它能使设计者尽最大可能的提高设计上的相对简单易用。在必须使用多线程的情况下,要对多线程间任务的并行效率和交互通信的手段以及交互造成的影响(如会不会死锁)有着一个清醒的认知。要始终明白,多线程是一种复杂的和无法预测的任务操作,要重点厘清以下几点:
- 并发拆分与并发模型的选择
这点非常重要,需要将相关任务拆解成合适的并发粒度并相应的使用相关的并发模型,如前面提到的生产者-消费者模型,Future模型以及主从模型、Actor模型等等 - 对数据、任务和调度等并行有足够的认知
所谓并发并行,不只是工作本身,还有需要操作的数据以及最线程执行过程中相关交互控制衔接等。就如工厂中的流水线作业,可能一个环节有问题,其它都受影响 - 重视线程的生命周期管理
这个非常重要,否则极有可能出现崩溃的现象。要确保线程的生命周期与相关协作的线程的生命周期吻合。对线程的创建、销毁和状态管理有着一个明确的操作过程。 - 严格管理线程间的通信
对需要多线程间进行交互通信的,需要确保数据的安全性,在此基础上提高处理的效率。关于利用各种同步操作、原子操作和CAS等技术,灵活的控制线程间的通信。 - 对常见多线程问题保持警惕
常见的如死锁、竞态等。同时,对线程的创建数量(重点是可能未及时回收相关线程资源)保持警惕,防止超限(合理的范围)。还要重视线程间任务的分配,防止出现负载不均的情况 - 尽量使用稳定的线程库或框架
这个不用细说,安全稳定的库和框架是方便快捷实现多线程开发的重要手段 - 重视异常和错误的处理
问题和异常的出现是不可避免的,但在多线程情况下如何进行线程相关窗口的恢复和任务的再执行以及相关数据的安全性保障等,都需要设计者提前规划。
至于到具体的设计应用的实践环节,需要明白以下几点:
- 善于利用种线程模型和库(含线程池)及线程框架
这一点非常容易理解,尽量不重复造轮子,有良好的久经考验的并行库,还是要引入为佳。当然,还是要根据实际情况,不能说因为想喝口水就修一个水库 - 善于利用各种性能工具包括多线程分析工具
比如在前面提到的profile、grpof、gperftools等来查找和定位热点线程和热点数据,从而反过来优化线程模型和线程设计开发
通过上述的分析和说明,设计者基本就可以把多线程并发设计的轮廓勾画出来,胸中有了丘壑,则不会在大方向上走弯路。
四、多线程的本质
从上面的分析其实可以得出多线程的本质即为充分利用硬件特别是CPU资源,从而提高程序的运行效率和交互响应能力。但是出乎很多开发者意料的是,线程的创建和运行并不是一件简单的事情,当然这不仅仅是针对开发者。对于操作系统以至于CPU本身来说,都是一件相当昂贵的行为。可能很多人不明白,这有什么昂贵可言?但实际上,线程的创建涉及到了操作系统内部的资源管理分配、相关资源的初始化调用以及最常听到的上下文切换的代价等等。
正如现实世界中,做一件事,增加一个人,可能会提高效率,但增加十个人,由于相应的管理人员和管理调度等的成本和开销增加,可能会降低效率。而计算机中的多线程也与其类似,相关的线程资源的创建(如内存分配)特别是线程的切换引起的上下文的处理,都可能会使多线程的效率不升反降。这就需要设计者在考虑实际的场景时能够兼顾全局,不要盯住一个点不放。
五、总结
多线程的复杂性和难以预料的执行状态,特别是不容易为人重视的线程的开销,都构成了设计和开发的一个痛点。接触多线程的开发设计者,往往有一个畏难——应用也挺简单——出问题发现复杂这么一个迭代的过程。特别是对于设计者来说,场景应用是不是适合使用多线程(IO密集型还是CPU密集型或混合等等),软(内核的版本、特性等)、硬件平台的支持性,软件开发语言的特性等等,这都需要仔细的考量。
参考: