为什么要找最佳线程数
1.过多的线程只会造成,更多的内存开销,更多的CPU开销,但是对提升QPS确毫无帮助
2.使用多线程就是在正确的场景下通过设置正确个数的线程来充分的利用 CPU 和 I/O 最大化程序的运行速度。
从两个方面和分析:
- CPU 密集型程序
- I/O 密集型程序
1、CPU 密集型程序
线程等待时间接近0
(1) 单核 CPU,所有线程都在等待 CPU 时间片。按照理想情况来看,四个线程执行的时间总和与一个线程5独自完成是相等的,实际上我们还忽略了四个线程上下文切换的开销。如图
所以,单核CPU处理CPU密集型程序,这种情况并不太适合使用多线程。
(2) 多核cpu,比如四核四线程,每个线程都有 CPU 来运行,并不会发生等待 CPU 时间片的情况,也没有线程切换的开销。理论情况来看效率提升了 4 倍:
如果是多核CPU 处理 CPU 密集型程序,我们完全可以最大化的利用 CPU 核心数,应用并发编程来提高效率。
CPU 密集型程序的最佳线程数就是:
线程数量 = CPU 核数(逻辑)
CPU 核数(逻辑)+ 1(经验值)。
2、I/O密集型程序
等待时间较长。
在进行 I/O 操作时,CPU是空闲状态,所以我们要最大化的利用 CPU,不能让其是空闲状态
单核 CPU 的情况下:
从上图中可以看出,每个线程都执行了相同长度的 CPU 耗时和 I/O 耗时,如果你将上面的图多画几个周期,CPU操作耗时固定,将 I/O 操作耗时变为 CPU 耗时的 3 倍,你会发现,CPU又有空闲了,这时你就可以新建线程 4,来继续最大化的利用 CPU。
综上:线程等待时间所占比例越高,需要越多线程;线程CPU时间所占比例越高,需要越少线程。
I/O 密集型程序的最佳线程数就是:
最佳线程数 = CPU核心数 * (1/CPU利用率) = CPU核心数 * (1 + (I/O耗时/CPU耗时))
举例:
如果cpu耗时:1,I/O耗时:9,那么cpu利用率10%,所以纯理论你就可以说是 10N(N=CPU核数,当然也有说 10N + 1的,1应该是backup。
如果几乎全是 I/O耗时,那么CPU耗时就无限趋近于0,所以纯理论你就可以说是 2N(N=CPU核数),当然也有说 2N + 1的,1应该是backup。
总结
1、多线程不一定就比单线程高效,比如大名鼎鼎的 Redis ,因为它是基于内存操作,这种情况下,单线程可以很高效的利用CPU。而多线程的使用场景一般时存在相当比例的I/O或网络操作
2、从定性到定量的分析的过程,在开始没有任何数据之前,我们可以使用上文提到的经验值作为一个伪标准,其次就是结合实际来逐步的调优(综合 CPU,内存,硬盘读写速度,网络状况等)
3、盲目的增加 CPU 核数也不一定能解决我们的问题,这就要求我们严格的编写并发程序代码
拓展
1、我们已经知道创建多少个线程合适了,为什么还要搞一个线程池出来?
- 不受控风险
- 频繁创建开销大
不受控风险
系统资源有限,每个人针对不同业务都可以手动创建线程,并且创建标准不一样(比如线程没有名字)。当系统运行起来,所有线程都在疯狂抢占资源,内存被无情榨干耗尽,这好比一个正木马程序(出现问题,自然也就不可能轻易的发现和解决)。
频繁创建开销大
内存分配,对象初始化,线程创建,线程状态保存等均需要资源