早期的计算机(单指令模式)
早期的计算机类似我们使用的计算器,你输入指令1+1,它会返回结果2。你不输入指令,它就是闲着不干活的。这样的话,大部分情况下计算机都是处于等待工作的状态,资源得不到合理的利用。
批处理模式(多指令模式)
单指令情况下,大部分时间里计算机都是闲着的,得想个办法让计算机有效的工作起来。如果我们把一个个指令先汇集起来,然后再交给计算机一次性的去执行,这样不就节省了我们输入指令的时间了么?
比如输入1+1的时候,计算机在等待用户输入完成然后返回2,输入1+2的时候也类似,计算机始终需要等待用户输入完成。如果将1+1和1+2一次性告诉计算机,那么计算机就会直接执行完1+1后,立马执行1+2,不会再去等待用户的输入完成,这样就合理利用了计算机的资源。
批处理的弊端
虽然批处理可以明显提升计算机资源利用率,但是也会出现一个问题。批处理里面会挨个执行很多指令,如1+1、1+2、2+2、3+4…等。假设执行到指令2+2时,需要做一些IO操作而陷入阻塞,这就导致后面的指令没法执行下去,CPU必须等待指令2+2执行完后,才能将时间片分配给后续指令。最终,CPU资源陷入等待,没法得到充分利用。
批处理解决方案
CPU陷入等待,这种情况显然又回到了最初的那种单指令模式,得想办法让CPU正常的干活。设想下,能不能让指令间进行切换呢?比如指令2+2阻塞了,那么CPU收回时间片,让后面的指令3+4去执行。想法很合理,但是要让指令间进行切换的话,肯定得将所有指令隔离起来。指令如何隔离?其实难度相当大,于是乎引入了进程。
进程
之前的批处理指令都是一次性加载到内存里面,要让指令隔离起来难度很大,但是有了进程就很好办了。计算机会给每个进程分配一块内存空间,并且每个进程的内存空间是不相互干扰的,这样就间接的通过内存将不同的操作隔离了起来。
指令间的切换转变为进程间的切换,也就是CPU时间片的切换。假设A进程执行到某个位置后,让出CPU资源给B进程先去执行,下次执行A进程时,应该是直接从原先的位置继续执行。这就意味着,由进程A切换到进程B的过程中,需要保存A进程在运行过程中的进度,这个就需要靠程序计数器了。
进程间运行都是基于CPU时间片去完成的,所以在任意一个时刻,对于单核CPU来说,只能有一个任务去执行。只是我们通过切换的方式去完成一个并行执行。由于CPU执行太快,所以切换的时候我们看不出来,以为是同时在进行。
关于CPU时间片,在举一个例子:
假设张三打开了两个记事本a.txt和b.txt,他在a.txt中输入一个字之后,再在b.txt中输入一个字,轮流输入,直至完成。总的看来,似乎在同时进行两篇文章的录入,可以说张三一边写a.txt一边写b.txt。但是具体到某个字时,其实就是沿着时间的前进,a.txt和b.txt交替进行了。而张三每个字输入所占用的这段时间,我们就可以称之为时间片。
进程的弊端
有了进程后,也会出现一个问题。就是进程在一个时间内只能干一件事情,如果想同时去干多件事情,进程就没法满足了。就像张三在a.txt里输入文字时,不能同时也在b.txt里输入文字,而只能a.txt里停止输入后,才能在b.txt里进行文字的输入。 又或者,假设张三在看电影,我们都知道电影的画面跟声音是同步的,但此时的张三只能光看画面而听不到声音,如果想听声音,那么画面就不会动了。很无奈,进程就只能干一件事,电影的画面和声音没法同时进行。
线程
认识到了进程的弊端之后,怎么解决才能保证多个事情可以同时进行呢?于是,线程就诞生了。进程把里面的多个子任务划分为每一个可以同时运行的线程,通过线程的切换去实现实时性。比如张三看电影,负责画面展示的由线程A来处理,负责声音同步的由线程B来处理。然后再结合CPU的多核,达到线程间的同步执行。