进程与线程

962 阅读6分钟

注意:以下是个人看很多文章,加上自己的理解,如有不对请指正

我们所熟知的windowlinuxmacOs被称为操作系统,我们所熟知的微信支付宝等软件叫做程序,运行在操作系统上。一个操作系统上可能运行着上千个程序。但是我们的操作系统只有一个,那么操作系统是怎么管理这些程序呢?

我们先想明白一件事情:

我们写的代码到底是怎么在计算机上跑起来的?

我们都知道,计算机只能识别0或者1这种二进制数据,代码执行的过程也就是计算机读取二进制指令并分析指令以及执行的的过程,那么我们编写的代码中有forwhileif这些语句,对于计算机来说他们根本是不可能识别出来这些关键字的。所以我们目前所使用的语言被称为人类语言,人类语言到机器语言需要经过一系列的翻译操作。

  • 有的编程语言要求必须提前将所有源代码一次性转换成二进制指令,也就是生成一个可执行程序(Windows 下的 .exe),比如C语言、C++、Golang、Pascal(Delphi)、汇编等,这种编程语言称为编译型语言,使用的转换工具称为编译器。
  • 有的编程语言可以一边执行一边转换,需要哪些源代码就转换哪些源代码,不会生成可执行程序,比如 PythonJavaScriptPHP、Shell、MATLAB 等,这种编程语言称为解释型语言,使用的转换工具称为解释器。

在网上盗了一张图:

编译型语言和解释型语言的执行流程

引入进程的原因

从上面我们可看出:经过一波操作后,就会将人类语言转化为机器语言。那么这个时候就有问题了,比如我开发微信的人,定义了一系列变量,我开发支付宝的人,也定义了一系列变量。那么如果我们不对这些变量作区分限制的话,就有可能会存在命名冲突的问题。在一些高级语言里,为了避免命名冲突的问题引入了namespace字段。那么在操作系统中该如何区分呢?

这是引入进程的原因之一。这也就是经常被大家所说的:进程是资源分配到最小单位。可以给每个进程分配不同的内存空间,作用就与高级语言的namespace功能类似。

在上文中,我们简单提到了指令这个东西。程序运行的过程就是读取指令的过程。但是在读取到某些指令的时候,比如:IO操作,在读取到指令后解析是个IO操作,然后计算机执行这个操作,可能这个操作写入磁盘的时间有一个小时之久,那么在这个时间之内,cpu已经不读取指令了。那么这个时候cpu完全是处于空闲状态,要是在没有进程的时候,只能等他执行完毕之后,在继续读取指令。没有进程,我所有的指令都是在一个大锅饭里。如果跳过的话,我就不知道到底是哪一个指令的时候,我跳过了它,执行到后面去了(就算是用临时变量存起来,你也不知道在他之后的是不是已经执行过了)

但是有了进程之后,当我遇到某个进程进行IO操作或者其他已经不再读取指令操作的时候,我就可以将他挂起,等到他IO操作完成或者其他已经不再读取指令操作的时候,等待下一次cpu空闲时间,在切回到他的上下文,继续从挂起的那个状态之后继续读取。就可以保证不浪费cpu的空闲时间。

因此经常会说:进程是资源分配的最小单位

引入线程的原因

线程是什么?要理解这个概念,需要先了解一下操作系统的一些相关概念。大部分操作系统(如Windows、Linux)的任务调度是采用时间片轮转的抢占式调度方式,也就是说一个任务执行一小段时间后强制暂停去执行下一个任务,每个任务轮流执行。任务执行的一小段时间叫做时间片,任务正在执行时的状态叫运行状态,任务执行一段时间后强制暂停去执行下一个任务,被暂停的任务就处于就绪状态等待下一个属于它的时间片的到来。这样每个任务都能得到执行,由于CPU的执行效率非常高,时间片非常短,在各个任务之间快速地切换,给人的感觉就是多个任务在“同时进行”,这也就是我们所说的并发(别觉得并发有多高深,它的实现很复杂,但它的概念很简单,就是一句话:多个任务同时执行)。多任务运行过程的示意图如下

引自[以操作系统的角度述说线程与进程]

img

程序之所以能运行,就是因为cpu在不断的从指令寄存器IR中取指令取执行。如果直接用进程操作的话:在当前进程还未进入到cpu上执行的时候,也就是处于就绪状态的时候。这个时候,他的指令是需要存储到一个存储在自身的私有堆栈上。当上一个进程被挂起,执行当前进程的时候,会将上一个进程的指令存储到他自身的私有堆栈上,然后将我当前进程的指令写入到指令寄存器IR中,然后将控制器PC指向上次暂停的地方,继续读取指令

一个程序要想运行起来,就必须经过解释或者编译转为机器语言,假如一个程序的代码量非常大,都直接从进程中写入指令寄存器IR的话,或者说挂起的时候,将指令寄存器IR上的指令暂存到进程的私有堆栈上,是非常费时间,并且处理了一些比不必要的操作。我完全可以只保留一个我当前这个代码块的执行的函数的指令

举个例子:

function a() {
  console.log('123');
}
function b() {
  console.log('456');
};
a();

可以看出,如果生成机器语言的话,肯定是有函数a和函数b的指令信息的,但是呢,我只执行了函数a,因此我在切换的时候,只需要将a的指令写入到指令寄存器中去。这样的话,在进行上下文切换的时候,需要暂存或者写入的操作少了很多。

图片引自:[以操作系统的角度述说线程与进程]

1-20171129153632862-1604985395

img

因此就出现了线程,线程是CPU调度的最小单位