进程和线程
进程:是计算机的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的「基本单位」(正在运行的程序就叫进程)
多进程:顾名思义就是启动多个进程,多个进程可以一块来执行多个任务。
线程:进程内一个相对独立的,可调度的执行单元;于同属的一个进程共享着进程中的资源(同一时间只能做一件事情)。
多线程:启动一个进程,在一个进程内部启动多个线程,这样多个线程也可以一块执行多个任务(也是通过调度来做的,如同多线程能做多任务一样,多线程也能做多任务)。
以上这些概念可能不是很好理解,我们可以把进程和线程比喻成我们生活中的工厂和工人:
进程好比一个工厂,线程好比工厂里的工人;
工厂的资源:系统分配的内存(独立的一块内存);
工厂之间相互独立:进程之间相互独立;
多个工人协作完成任务:多个线程在进程中协作完成任务;
工厂内有一个或多个工人:进程内由一个或多个线程组成;
浏览器的多线程
浏览器是多进程的,我们常说的「浏览器渲染引擎」就是进程中的一个。
浏览器渲染引擎中常驻的线程有:
JS
引擎线程:该引擎主要是解释和执行JS
,另外它是单线程,这主意是为了避免两个JS
文件同时操作同一个DOM
引起混乱GUI
线程:绘制用户界面,它和JS
引擎是互斥的HTTP
网络请求线程- 定时器线程
- 浏览器事件处理线程
HTTP
线程、定时器线程和浏览器事件处理线程统称为Web APIs
,主要是一些异步操作。
JS 的运行原理
单线程由于是一个“工人”在工作,数据处理能力自然就会慢很多。
如果遇到数据量大的时候怎么办呢?
目前的解决方案有:1、SSR
服务端渲染 2、webworker
向JS
单线程申请子线程,子线程由浏览器进程开启
另外由于JS
引擎是单线程的,如果遇到等待的场景(例如:网络请求,如果一直不返回数据,那么页面就会一直卡着不动)就需要用到异步的方案。
异步可以理解为:同一个时间处理两个事情。**JS**
引擎通过「事件驱动」的方式模拟来实现异步(事件驱动是基于浏览器提供的环境,不同的环境基于事件驱动的模型可能不太一样)
另外,所有的异步代码全部是以回调函数的方式出现的,并不是所有的回调函数都是异步代码。
// setTimeout 就是异步函数
setTimeout(function(){
// do...
}, 1000)
// Array 的 sort 方法是同步代码
arr.sort(function(a, b){
return a - b;
})
事件循环
JS
引擎并不是单独运行的,它是在一个宿主环境中运行的,对于大多数开发人员来说,宿主环境就是典型的web浏览器或Node.js
。
所有环境中的共同点是一个称为事件循环的内置机制,它处理程序的多个块在一段时间内通过调用JS
的引擎执行。
:::info
先来了解几个概念:Call Srack
:调用堆栈,也可以理解为主线程Web APIs
:异步任务,也可以理解为子线程或者待回调的任务Callback Queue
:任务队列,子线程中的程序达到一定条件后会添加任务队列中Event Loop
:事件循环,先看主线程任务有没有执行完成,如果执行完成再看任务队列,如果任务队列中有任务,那么就会将任务推入到主线程中执行
:::
举例 🌰:
console.log("Hi");
setTimeout(function () {
console.log("cb1");
}, 5000);
console.log("Bye");
初始化状态都为空,浏览器控制台是空的的,调用堆栈也是空的console.log('Hi')
添加到调用堆栈中
执行console.log('Hi')
console.log('Hi')
从调用堆栈中移除setTimeout(function cb1() { ... })
添加到调用堆栈setTimeout(function cb1() { ... })
执行,浏览器创建一个计时器计时,这个作为**Web api**
的一部分setTimeout(function cb1() { ... })
本身执行完成,并从调用堆栈中删除console.log('Bye')
添加到调用堆栈
执行console.log('Bye')
console.log('Bye')
从调用调用堆栈移除
至少在 5 秒之后,计时器完成并将**cb1**
回调推到回调队列。
事件循环从回调队列中获取**cb1**
并将其推入调用堆栈。
执行**cb1**
并将**console.log('cb1')**
添加到调用堆栈。
执行console.log('cb1')
console.log('cb1')
从调用堆栈中移除cb1
从调用堆栈中移除
ES6
指定了事件循环应该如何工作,这意味着在技术上它属于JS
引擎的职责范围,不再仅仅扮演宿主环境的角色。这种变化的一个主要原因是ES6
中引入了Promises
,因为ES6
需要对事件循环队列上的调度操作进行直接、细度的控制。
setTimeout 是怎么工作的?
setTimeout
不会自动将回调放到事件循环队列中。它设置了一个计时器。当计时器过期时,环境将回调放到事件循环中,以便将来某个标记将接收并执行它。
setTimeout(myCallback, 1000);
这并不意味着**myCallback**
将在 1000 毫秒后就立马执行,而是在 1000 毫秒后,**myCallback**
被添加到队列中。但是,如果队列有其他事件在前面添加回调则必须等待前面的执行完后再执行**myCallback**
(所以可能会存在一点点误差)。
调用setTimeout
的时候 0 毫秒作为第二个参数只是推迟回调将它放到回调队列中,直到调用堆栈是空的。
console.log('Hi');
setTimeout(function() {
console.log('callback');
}, 0);
console.log('Bye');
// Hi
// Bye
// callback