1.什么是上下文切换?
操作系统利用了时间片轮转的方式,CPU给每个任务都服务一定的时间,当前任务在执行完CPU时间片后,切换到另一个任务之前会保存自己的状态以便下次在切回这个任务时,可以在加载这个任务的状态。任务从保存到再加载的过程就是一次上下文切换。上下文切换意味着会消耗大量的CPU时间。
2.线程是如何执行的?
线程的执行是由CPU进行调度的,一个CPU在同一时刻只会执行一个线程
3.为什么要使用多线程?
3.1.从计算机底层来说 线程可以比作是轻量级的进程,是程序执行的最小单位,线程间的切换和调度的成本远远小于进程。另外,多核CPU时代意味着多个线程可以同时运行,这减少了线程上下文切换的开销。 3.2.从系统并发处理来说 现在的系统动不动就要求百万级甚至千万级的并发量,而多线程并发编程正式开发高并发系统的基础,利用好多线程机制可以大大提高系统整体的并发能力以及性能。 并发能力主要体现在吞吐量:吞吐量越大,意味着程序能处理的请求越多 性能主要体现在延迟上:延迟越短,意味着程序执行得越快,性能也就越好。 使用多线程的主要目的:降低延迟,提高吞吐量
4.使用多线程带来的问题
并发编程的目的就是为了能提高程序的执行效率提高程序运行速度,但是并发编程并不总是能提高程序的运行速度的,而且并发编程可能遇到很多问题,如:内存泄漏,上下文切换,死锁等。还可能受限于硬件和软件的资源闲置问题。
5.多线程的应用场景
在并发编程领域,提升性能本质上就是提升硬件的利用率,也就是提升I/O的利用率和CPU的利用率,需要解决CPU和I/O设备综合利用率的问题。
这里借用某老哥画的一个图来说明下怎么合理使用多线程。 下图中,我们只有一个线程A,线程进行CPU操作后,接着I/O进行操作,如同一条线,这时候我们不难发现,当我们进行I/O操作的时候,CPU是处于空闲状态的。 我们在看如下这个图,我们发现当线程A正在使用CPU的时候,线程B正在使用I/O,而当线程B使用CPU的时候,线程A正在使用I/O,因此对于如下这个图,我们不难发现,那就是CPU一直处于工作状态。 而我们要想通过多线程来提升我们的性能和并发能力,那就是在减少上下文切换的时候,充分的利用CPU资源以及I/O资源。
6.CPU密集、I/O密集任务怎么判断?
6.1.CPU密集任务
CPU密集型会消耗掉大量的CPU资源,例如需要计算圆周率、大量的计算、视频渲染、仿真等。计算密集型任务的特点是要进行大量的计算,消耗CPU资源,全靠CPU的运算能力。这种计算密集型任务虽然也可以用多任务完成,但是任务越多,花在任务切换的时间就越多,CPU执行任务的效率就越低 这个时候CPU就卯足了劲在运行,这个时候切换线程,反而浪费了切换的时间,效率不高。
如果把我们的大脑比作CPU,当你在上物理课,数学课、算法课的时候,其实这时候你的大脑处于高负载工作状态,全精力投入你都不一定能听的懂老师在说什么。然后这个时候突然你想起了来了,明天你要去面试,你要准备很多面试八股文。然后你旁边的人正在看英雄联盟S11总决赛,EDG和DK正在大杀四方,你内心激动的不得了,忍都忍不住的看了好几眼。 那么这时候你只有三种选择,要么老老实实上课,要么准备八股文面试,要么全身心的观看比赛,为EDG选手的操作连连称赞。这时候的你是绝对不能三件事同时去做的。这时候你的大脑压根没有资源让你能同时做这三件事,如果你非得都要干,也就是开启多线程,结果就是观看比赛体验不好,老师讲的课没听懂,该准备的八股文没背下来。
6.2.I/O密集任务
IO密集型,涉及到网络、磁盘IO的任务都是IO密集型任务,这类任务的特点是CPU消耗很少,任务的大部分时间都在等待IO操作完成(因为IO的速度远远低于CPU和内存的速度)。对于IO密集型任务,任务越多,CPU效率越高,但也有一个限度。常见的大部分任务都是IO密集型任务,比如Web应用。
还是以我们的大脑当作CPU,此时的你正在坐在电脑面前听着歌;时不时瞄一眼你的微信,因为你给你女神发了消息,你女神还没回复你;想到晚上下班在地铁上又是一个小时,你庆兴你们单位没有国美那么变态,去监控每个人上网使用流量,因此你打算下载一部电影,准备在地铁上看;摸了摸脖子有点酸痛,常年坐在工作岗位上,身体一天不如一天,然后你去茶水间泡了一大杯枸杞…
此时划水的你,一边听歌,一边等女神回复消息,一边下载电影,一边喝枸杞…然而都这样了,你的脑袋瓜子,仍有余力干其他的事。比如拿着手机翻看你女神的朋友圈;比如吐槽下你领导和客户是傻逼;和产品经理在撕逼,这个功能,我做不了;和测试小姐姐说,这不是BUG,是需求不明确…
这就是I/O密集任务,因为你大脑此时任然不饱和,还能干很多事,换句话说就是,你太闲了。
创建多少线程合适?
一般来说,对于创建多少线程,我们先要确定我们属于CPU密集型还是I/O密集型。
CPU密集型
对于CPU密集型的计算场景,也就是使用CPU是比较多的,这时候不建议开过多线程,因为本身CPU使用就很频繁了,这时候增加多线程,CPU资源除了不够用以外,还会导致上下文切换。因此CPU密集型任务开启多少线程主要还是取决于CPU的核数来决定。
理论上线程的数量 = CPU核数 就是最合适的 工程上线程的数量 = CPU核数 + 1,这样的话,当线程因为偶尔的内存页失效或其他原因导致阻塞时,这个额外的线程可以顶上,从而保证CPU的利用率。
I/O密集型
对于I/O密集型的计算场景,也就是说大多时候CPU是处于空闲状态的,这时候就可以增加多线程来提升CPU的利用率。因此这个时候,即使多线程会产生上下文切换,但是产生的影响可能对于CPU的利用率提升来说,就显得微不足道了。 针对单核处理器 一般我们先去计算出I/O和CPU的耗时资源比,比如I/O读写需要5秒,CPU计算需要1秒,也就是说这个时候,我们CPU对比I/O,我们有5倍的时间里面,都是属于空闲的,因此此时想提升CPU利用率,我们完全可以开启5个线程。 单核处理器最佳线程数 = 1 + ( I/O耗时 / CPU耗时 )
除了使用CPU和I/O的耗时来计算,我们也可以使用CPU利用率来计算,CPU等待时间除以工作时间,我们就知道CPU处于等待时候的占比了。空闲占比乘以利用率,即可以 求出最佳线程数 单核处理器最佳线程数 = CPU利用率×( 1+ 平均等待时间 / 平均工作时间 ) 针对多核处理器 上面我们计算了单核处理器,那么计算多核处理器就比较简单了,就是多核 x 单核线程数 多核处理器最佳线程数 = CPU核数 × [1 + ( I/O耗时 / CPU耗时 )]
当然除了采用耗时来计算,我们也可以采用利用率和等待时间占比 多核处理器最佳线程数 = CPU核数 × CPU利用率 × ( 1+ 平均等待时间 / 平均工作时间 )
|