撬开多线程的大门——学习多线程必须掌握的基本概念

时间:2022-10-14 15:08:06

1.进程

进程的概念从字义上理解相对还是比较抽象的,但进程实际上对我们并不陌生,可以说它无时不刻的伴随着我们的生活。当你每天上班打开电脑,运行微信与好友通讯、运行浏览器阅读网页新闻等,这一些将程序运行起来的操作,都属于创建了一个进程。并且我们可以对同一种程序重复运行多次,这意味着一个程序可以创建多个进程,例如我们时常针对Word这一种程序,反复的运行从而阅读不同的文档。

进程是程序的一段执行过程;进程是一个正在执行的程序;进程是程序的实例。程序是静态的,通过运行程序就会产生动态的进程。总之,诸如此类。

它是操作系统动态执行任务的基本单元,是操作系统进行资源分配的基本单位。在这一点上就类似于军事战役,司令就像操作系统,它不会对某个士兵下达命令或分配物资,而是以部队为单位下达命令并分配物资,调度各种部队来指挥作战,这里部队的调度分配就有点类似于进程。

从结构上,我们可以想象操作系统是间大房子,众多程序运行同在一件大房子里,如果没有隔离的房间,势必会错乱不堪。而进程会起到类似“房间”隔离的作用,让操作系统的运行环境更加稳定,即使一个程序失败也不会影响另一个程序。从这一点上,我们可以认为,进程提供了程序执行的独立环境和安全边界。

正在运行的操作系统(你现在的电脑)就是由各种进程的活动构成,你可以打开“任务管理器”,可以了解你当前计算机的所有进程,以及进程的资源分配情况。

撬开多线程的大门——学习多线程必须掌握的基本概念


2.线程

根据上文中的介绍,总而言之,我们可以将进程看作是一个正在运行的程序。既然是运行的程序,必定会对程序有所期许(指示/任务)。试想下,你打开某个程序使它运行是为了什么,你如果你打开“QQ音乐”肯定希望它播放一首你喜欢的歌曲,你如果打开“饿了么”你肯定你希望点一份外卖。对于以上这些,你对程序下达的“指示/任务”,实际上投射到程序当中,就会对应产生一条线程。这一点可以说明,线程是进程运行过程中执行的任务。

一个程序的运行对于用户而言,往往感知不到代码执行的存在,用户通常实现某个功能就点击相应的按钮。实际上,在点击按钮的背后,进程不光会产生一个线程,线程会根据对应的操作选择执行一条代码的路线,通过执行这条代码路线来实现相应的功能。

撬开多线程的大门——学习多线程必须掌握的基本概念

每一个线程都运行在一个操作系统的进程中,因此进程可以看作是线程的容器。每一个进程都必定包含一个用做程序入口点的主线程,该主线程会在程序运行起来时自动创建。除主线程之外,每个进程还可以通过编程方式创建额外的次线程(工作者线程),无论是主线程还是次线程都属于进程中的一个独立执行单元,并且在多个线程之间它们能够同时访问进程中的共享数据。


 3.并发

当前线程执行结束之后,才能执行下一个线程。也就是说你打开了一个音乐程序(进程)播放周杰伦的“七里香”(线程),如果还需要打开记事本程序进行打字,则必须要等到“七里香”这首歌播放结束后才能进行。

找到影子,并发也是如此。想想你在工作时,一边听着音乐一边打字的样子;想想你在午餐时,一手拿着手机一手拿筷子往嘴里塞事物的样子;以上的这些现象就属于并发,即在同一个事物,在同一个时间阶段内,开展多项任务。

的执行多个线程,也就是这个任务根据分配的时间片执行一会儿(10ms),在切换到另一个任务根据相应时间片再执行一会儿(10ms)。下图展示了两个线程并发执行的过程:

撬开多线程的大门——学习多线程必须掌握的基本概念

由于并发的方式促使线程切换速度很快,所以并发的执行通常对于用户的感觉而言,就像是多个任务并行一样,以致于产生了一种多个任务同一时间执行的假象。这一点听上去可能有点是是而非,你需要将并发的多个任务看作是在同一个时间阶段内执行的,而非是某个具体的时间点同时执行的。例如,两个任务都是在0到60秒这个阶段中完成的,但如果比较具体的执行时间点,一个任务某个执行时间点是08:30:12,另一个任务某个执行时间点则会是08:30:56。说白了就是,并发就是“一心二用”。这是因为单核CPU的计算机并没有能力在同一时间点运行多个线程。

。如果单核CPU处理的线程过多,CPU则会花费大量时间在这些线程之间进行切换,这会导致程序的性能下降。


4.并行

基于单核CPU的短板,并随着计算机硬件的发展,CPU迈入了多核时代,双核、四核、八核已屡见不鲜,甚至还有高达几十核的CPU。多核CPU不在局限于和单核CPU那种“一心两用”的工作方式,而是可以真正实现同一时间点执行多个线程(任务),达到“双管齐下”的效果。多核CPU的每个核心都可以独立地执行一个线程,并且多个核心之间不会相互干扰。因此,多核CPU在不同的核心上,在同一时间点,分别执行一个任务的这种方式,称之为并行。

例如,同样是执行两个任务,双核 CPU 的工作状态如下图所示:

 撬开多线程的大门——学习多线程必须掌握的基本概念

通过上图我们可以看出,双核CPU可以在同一个时间各自执行一个任务,和单核CPU在两个任务之间不断切换相比,它的执行效率更高。需要注意的是,上图中CPU的核数与线程(任务)数刚好匹配,这是个理想状态,如果线程数大于了CPU的核数,那么计算机会按照什么样的方式执行呢?你可以思考一番,然后在下文中找到答案。


5.并发&并行

,对于这一点你可以打开任务管理器,通过查看CPU线程数就可以证实。

撬开多线程的大门——学习多线程必须掌握的基本概念

所以对于现实中存在的这种情况(核数低于线程数),计算机这个时候对于线程的处理,会同时存在并发和并行两种情况:所有的CPU核心都会并行工作,其中每个核心还会进行并发工作。例如一个双核 CPU 要执行四个任务,它的工作状态如图所示:

 撬开多线程的大门——学习多线程必须掌握的基本概念

上图中每个核心并发执行了两个线程,两个核心并行就执行了四个任务。当然也可以一个核心执行一个任务,另一个核心并发执行三个任务,具体的分配还是要取决于操作系统的调度算法,以及每个线程的处理状态。

小结

的工作方式,只会在多核CPU处理的硬件条件下,并且线程数与核数相等情况下出现,它代表了多个核心同时执行多个任务的能力。在多核CPU中,并发核并行通常都会同时存在,两种工作方式的结合,会有效的提升计算机执行程序的效率。


6.概念类比

让这些概念可以形象化的展示在你面前。

 

进程

 

线程

程序通过开启进程活动了起来,那么活动的进程中必定会开展相应的任务。这就等于你饭店在安置后客人就坐后,需要为客人烹制美味菜肴。我们来看看这个家庭点的菜肴:鱼香肉丝、湖北藕汤、剁椒鱼头、夫妻肺片。这些菜肴的制作通常对于一个标准化的饭店而言,都会在厨房中会划分不同的制作区域。例如,鱼香肉丝要在灶台区域、剁椒鱼头要在蒸柜区域。

当厨房要烹制某个菜品时,厨师就会根据菜肴的制作类型(炒、汤、蒸),到达指定的区域进行烹饪。对于这个现象,就像程序为实现不同的功能,会选择不同的代码路径执行一样。不同的菜肴要找到相应的区域进行烹制,并且为客人制作菜肴是饭店的主要任务,综上所述,我们可以将饭店为客人制作菜肴的任务,看作是进程中执行的线程。

 撬开多线程的大门——学习多线程必须掌握的基本概念

并发

先别着急流口水,在点菜之后,我们将目光转向厨房。此时的厨房只有一名厨师在岗,所以他一个人将面临多道菜的制作,这名厨师并不打算一道菜一道菜的制作,因为他担心如果上菜太慢会导致:1.客人在吃前几道菜时就饱了,客人会放弃后面的菜;2.由于菜的间隔时间过长,最后一道菜上时第一道菜就已经凉了。所以他打算一个人先同时开展两道菜的制作,于是他在藕汤进行煨煮时,起锅烧油去锅里炒鱼香肉丝;当鱼香肉丝烧至入味时,去给藕汤进行调味,利用两个菜肴的空挡不断切换,最后当鱼香肉丝出锅时,藕汤也已经煨好了。以上这名厨师同一时间阶段内做多道菜的方式,就类似于与并发。

 

并行

此时老板来到厨房,看到你非常卖力的为客人准备菜肴,加上客人也有点着急,于是老板穿上了白大褂,戴起来高帽子,打算加入你的行列。此时的厨房就已经有两名厨师了,此时客人还只剩两道菜(剁椒鱼头、夫妻肺片)没有上。显然目前最佳的制作方式就是,两个厨师同时进行菜肴的制作,每个人负责一道菜肴。那么对于以上两名厨师同时进行菜肴的工作方式,就类似于并行。

 

并发&并行

。对于这种情况,就类似CPU同时使用并发加并行两种方式开展任务。


 7.多线程编程

应用程序可以使用编程语言实现多线程的编码,从而实现在一个进程中创建多个线程,来完成一个程序中多项任务的同时处理。

 

目的

是为了同步完成多项任务。你可以试想下,你正要筹备一场年夜饭的食材。如果采购各式各样的食材全都是你一个人去完成,那么年夜饭的准备时长和开饭时间必定会延长。如果你安排你的家人进行协作,那么你的家人可以和你同时去购买不同的食材,这样一来会有助于节省你购买食材的时间。在这个例子中,你安排家人协作你购买食材,实际上就和编程中使用多线程的目的是一致的,编程中通过多线程会助于改善程序的总体响应性。

 

切换

。每一个线程都需要分配独立的堆栈空间(耗费内存,如一个线程约占用1MB堆栈空间)。并且CPU对线程的切换需要保存很多中间状态、数据等,所以单个进程中的线程过多的话,性能反而会下降,CPU需要花费不少时间在各个线程之间来回切换,以致于耗费本该属于程序运行的时间。

 

共享

地去写,而不能同时去写,如果同时去写,可能写进去的数据就会出现互相覆盖等数据不一致的错误。

 

执行

从编码的角度来看,线程的执行仿佛是我们调用相应的函数来完成的,但实际上并非如此。我们调用线程执行的代码,并不会在程序执行这段代码时立即执行。准确的说,这段调用线程执行的代码,仅仅是通知操作系统尽快地执行这个线程。线程具体的执行,是由操作系统,根据线程调度程序的分配机制决定的。


结语

本文的基本概念只能作为多线程学习的一个开端,后续我将持续产出针对多线程应用的知识。想要将多线程技术更好的运用起来,可谓是,“路漫漫其修远兮”。多线程的技术熟练运用,不光是高级开发人员与中级开发人员之间的一道分水岭,它还是很多实际项目必须采用的一种技术方式。项目不单单只满足于功能而已,对于运行效率的提升,多线程技术的涉猎是不二法则。

戒骄戒躁,千万不要急于求成,不要以为多线程是一个很小的话题。多线程其实是一个很大的话题,请各位读者要稳扎稳打,一步一个脚印地把多线程学好,这会终身受益。