本文主要思想来自 Java虚拟机并发编程 (薛笛 译)
为什么要用并发
并发是再在有限的资源下提高性能的有效手段。
当然现在互联网环境下并发访问的现象也比比皆是。
但是本文并不涉及处理并发访问,而是使用并发手段解决复杂任务的策略。
另外关于并发和并行的区别,以及一些基础的线程知识可以参见我之前的博客。
并发策略的意义
并发并不能保证程序效率的提高,相反可能会出现死锁,幻读,脏读等情
况。而采用什么样的并发策略是提高程序性能的关键。
程序的两大分类
IO密集型程序
顾名思义,这类程序的处理IO操作的时间较长。
而处理IO的情况下,程序会阻塞,等待 IO的结果。
这种情况下我们将程序挂起,执行其他的程序是符合我们性能优化的目标的。
eg. 从第三方API读取天气信息。程序要做的大量工作就是从远程将天气信息读取出来。
计算密集型程序
这类程序的主要工作是进行大量的运算。
这类程序和IO密集型的主要区别就在于划分成子任务的情况下,每个任务都可以是就绪状态,并且可以竞争CPU。
eg.某研究所的实验数据统计
分工原则
理解了上述的两种程序,就是本文的主要内容了,当然在这之前你要对操作系统中的线程,以及CPU的核心有足够清楚的认识
确定线程数
我们进行任务分工的时候首先要考虑的是创建多少个线程来进行我们的任务合适
当创建的线程数少于我们的CPU核心数时,我们的程序只会使用CPU的部分性能。这显然不是我们想要的
所以第一条原则
程序创建的线程数不应少于CPU的核心数
其次当应用程序属于IO密集型程序时,当一个任务执行IO操作时,其线程会被阻塞,所以CPU处理器可以立即进行上下文切换。转而处理其他任务。
而但应用程序属于计算密集型是,创建更多的线程反而会导致,多个线程处于就绪状态去竞争CPU处理器。会产生频繁的上下文切换,造成不必要的损失。
所以为了更好的确定程序应该创建的线程数量。我们需要知道两个参数 处理器可用核心数 和 任务的阻塞系数
可用核心数可以用一些性能分析工具确定。
主要的就是任务的阻塞系数,将计算密集型任务的阻塞系数设为0,而IO密集型的设为1。我们就可以得到第二条原则
线程数=CPU可用核心数*(1-阻塞系数)
确定任务数
将复杂的任务分解,并分配给之前确定好的线程去执行。为了减少执行时间, 我们的理想状态是,所有线程的任务量一样重,能够同时结束时最好的了。
任务量其实就是任务的复杂程度。有时候将任务分解为相等的子任务并平均分配到各个线程上要花费的心思很多。
于是我们将任务进行尽可能的拆分尽力产生足够多的工作来使处理器一直不停的工作更有效。
这便是分工的第三条原则。