JS进阶系列-第一篇-什么是单线程?

Posted by Kylen on 2019-07-10

前言

程序的问题,从语言本身到具体执行,很容易牵扯到计算机、操作系统的问题,再深挖就容易牵扯到物理和数学问题,再挖就只能用祭出量子力学了。

我们不得不承认透过编程可以一窥世界本质。而计算机无疑是世界上最伟大的发明之一。

这里就简单的挖一下单线程。

同步、异步 阻塞、非阻塞 并行、并发

同步和异步关心的是消息通信机制
同步:发出一个调用时,没有得到结果,该调用就不返回
异步:发出一个调用,直接返回调用,等到有了结果在通知调用者,或者直接使用回调函数

阻塞和非阻塞关心的是程序在等待调用结果时的状态

一个生动描述同步、异步、阻塞、非阻塞的例子:

老张爱喝茶,废话不说,煮开水。出场人物:老张,水壶两把(普通水壶,简称水壶;会响的水壶,简称响水壶)。1 老张把水壶放到火上,立等水开。(同步阻塞)老张觉得自己有点傻2 老张把水壶放到火上,去客厅看电视,时不时去厨房看看水开没有。(同步非阻塞)老张还是觉得自己有点傻,于是变高端了,买了把会响笛的那种水壶。水开之后,能大声发出嘀~的噪音。3 老张把响水壶放到火上,立等水开。(异步阻塞)老张觉得这样傻等意义不大4 老张把响水壶放到火上,去客厅看电视,水壶响之前不再去看它了,响了再去拿壶。(异步非阻塞)老张觉得自己聪明了。

并行:逻辑上的同时发生,多个程序在单个CPU上执行,同一时刻,只有一个任务执行。
并发:物理上的同时发生,多个程序在多个CPU上执行,同一时刻,多个任务执行,彼此之间互不干扰。

进程和线程

关键概念:进程是资源分配的最小单位,线程是CPU调度的最小单位

我们知道一个人在同一时刻只能干一件事,你不能在吃饭的同时喝水,因为你只有一张嘴。我们知道计算机的运行离不开CPU,计算机的运行过程其实就是CPU调度资源计算使用的过程。也就是说我们在计算机上执行任意任务都需要CPU参与,那CPU忙的过来吗?

现代计算机CPU基本都是多核的,多核可以简单理解为每个核心可以单独处理任务。也就是说干活的人多了,以前一个人干,现在多个人干。但计算机中同时运行任务数量远比多核CPU的核心要多。以前的单核CPU和现在的多核CPU是如何处理多任务的呢?

我们必须要知道一个事实:CPU很快,快到什么地步,就像一个后宫真的有三千佳丽,依然毫无压力的皇上。当多个任务要执行时,操作系统会让这些任务交替执行,基于什么规则我们不需关心。每个任务的执行时间很短,没错,因为CPU很快,假设任务1执行0.01秒,切换到任务2,任务2执行0.01秒,切换到任务3…这样反复执行下去,从使用者的角度看,所有任务就像是在同时执行。单核CPU就是这样处理多任务的,对于多核CPU,操作系统会把很多任务轮流调度到每个核心上。

对于操作系统来说,一个任务就是一个进程,比如打开浏览器就是启动一个浏览器进程,打开一个记事本就是启动了一个记事本进程,打开一个Word就启动一个Word进程。但是进程的颗粒度太大,一个进程也可能同时干多件事,比如Word,你可以同时进行打字、拼写检查、打印等事情。一个进程内部可以有多个子任务,这些颗粒度更小的任务我们就称为线程。从表象上我们是这样区分的。

从CPU的角度,执行任务的过程是:加载程序A的上下文,开始执行A,保存A的上下文,调入下一个要执行的程序B的上下文,然后开始执行B,保存B的执行上下文…在这个过程中,A进程 = CPU执行程序A的时间 = CPU加载程序A上下文 + CPU执行 + CPU保存A上下文。我们说进程的颗粒度太大,一个进程也可能有多个任务,比如A进程实际分为a、b、c三个子任务,称为a、b、c三个线程,线程可以共享进程的上下文环境。相较于直接把A进程依照子任务直接拆分成3个进程这就是优势了。CPU执行线程a必须要先加载进程A的上下文环境,然后执行b或者c线程的时候切换的资源成本就更低。

总结:

  • 单核CPU同一时刻只能干一件事
  • CPU速度很快,任务很多
  • 操作系统让各个任务交替执行,CPU一个个临幸(这其实就是并发)
  • 一个程序至少有一个进程,创建进程会分配资源
  • 一个进程可能有多个子任务,一个子任务就是一个线程
  • 线程共享进程的资源。
  • 线程和进程本质都是CPU工作时间段的描述。
  • 进程是资源分配的最小单位,线程是CPU调度的最小单位

真正的并行执行多任务只能在多核CPU上执行。单核CPU只能做到并发,当然我们在开发程序时若是写多任务不需要关心系统是单核还是双核,因为无论CPU能并发所以一定可以做到支持多任务。多任务实现的方案也就是:

  • 多进程,每个进程只有一个线程
  • 一个进程,多个线程
  • 多进程+多线程

都知道JS是单线程语言,这也就意味着一个JS程序运行时只会有一个主线程,同一时间只能做一件事。做为浏览器脚本语言从用户的角度出发设计成单线程语言是很合理的事,因为同一时刻,用户的操作是唯一的。JS作者布兰登·艾奇(Brendan Eich)初始就是这么设计的。

不过也正是因为单线程,某一时刻只能执行一个任务,若是遇到耗时的任务就容易阻塞。所以JS常提及的就有异步和回调的问题以及JS事件循环的问题。

参考链接

怎样理解阻塞非阻塞与同步异步的区别?
廖雪峰 Python教程 进程和线程
线程和进程的区别是什么?
多进程架构
关于JavaScript单线程的一些事