进程_线程_js事件循环
进程与线程
进程(process)
计算机已经运行的程序,是操作系统管理程序的一种方式;我们可以认为:启动一个程序,就会启动一个或者多个进程。
线程(thread)
操作系统能够运行运算调度的最小单位,通常情况下它被包含在进程中(进程是线程的容器);同样,我们可以认为:每一个进程,都会至少启动一个线程来执行程序中的代码,这个线程被称为主线程。
当进程中的线程获取到时间片时,就可以快速执行我们编写的代码。
js线程
JavaScript是单线程的,但是JavaScript的线程应该有自己的容器进程︰浏览器或者Node。
- 目前多数的浏览器其实都是多进程的,当我们打开一个tab页面时就会开启一个新的进程,
- 如果浏览器为单进程,一个页面卡死而造成所有页面无法响应,整个浏览器需要强制退出;
- 每个进程中又有很多的线程,其中包括执行JavaScript代码的线程;
- 如果当前js线程时耗时操作,就会造成代码阻塞。
- 解决方法:
- 浏览器的每个进程是多线程的,那么其他线程可以来完成这个耗时的操作;比如网络请求、定时器,我们只需要在特性的时候执行应该有的回调即可。
事件循环
在浏览器环境下,js单线程属于浏览器进程;在Node环境下,js单线程属于Node进程。
js本身是单线程的,意味着js不适合做耗时操作,否则会导致程序阻塞;
那么,耗时操作就交给浏览器或者Node创建的其他线程来完成,并将处理结果以回调函数的形式,放入浏览器或者Node维护的事件队列中,js在合适的时机来执行这些回调函数;
js在执行回调函数时,也有可能会遇见耗时操作,同理,这些耗时操作一就会被加入到事件队列中已在合适的时机被js执行。
宏任务&&微任务
宏任务(macrotask)
浏览器的宏任务:ajax回调、定时器、DOM事件回调、UI Rendering(UI渲染完之后的回调);
Node的宏任务:setTimeout、setInterval、IO事件、setImmediate、close事件。
微任务(microtask)
浏览器的微任务:queueMicrotask的回调、promise.then的回调、MutationObserver的API;
Node的微任务:Promise的then回调、process.nextTick的回调、queueMicrotask 的回调。
但是,Node中不简单是一个宏任务和微任务队列,
微任务队列:
- nextTick queue:process.nextTick
- other queue:Promise的then回调,queueMicrotask
宏任务队列:
- timer queue : setTimeout、setInterval ;
- poll queue : IO事件;
- check queue : setImmediate ;
- close queue : close事件;
Node的宏任务和微任务的执行顺序:nextTick queue、other queue、timer queue、poll queue、check queue、close queue
总的执行过程
注意:在执行宏任务之前,需要确保问任务队列被清空;new Promise是同步代码。
- 首先执行js顶层代码(同步代码);
- 执行的过程中,遇见宏任务,将该宏任务加入到宏任务队列;
- 遇见微任务,将该微任务加入到微任务队列;
- 顶层代码执行完毕,依次执行微任务队列中的任务,清空微任务队列;
- 执行宏任务队列最先加入的宏任务,同时执行步骤2、3;
- 执行微任务队列中的任务,清空微任务队列,执行步骤5;
浏览器
浏览器维护着两个队列:宏任务队列和微任务队列。
浏览器的事件循环(简):
Node
浏览器中的EventLoop是根据HTML5定义的规范来实现的,不同的浏览器可能会有不同的实现,而Node中是由libuv实现的。
Node架构图:
- libuv中主要维护了一个EventLoop和worker threads(线程池),其主要是为Node开发的,现在也被使用到Luvit、Julia、pyuv等其他地方;
- EventLoop负责调用系统的一些其他操作∶文件的IO、Network、child-processes等
服务器相对于浏览器最大的区别:I/O操作。
js本身并不会进行发起网络请求,连接数据库写入数据库,读取文件等操作,这就交给libuv来执行,相当于libuv给js提供了一些接口供其来调用,其本质是libuv执行了系统调用,并将执行结果以回调函数的形式返回,js在合适时机从事件队列拿取这些回调函数去处理结果。
Node中的事件循环被划分为很多阶段:
- 定时器(Timers):本阶段执行已经被setTimeout()和setInterval()的调度回调函数。
- 待定回调(Pending Calback):对某些系统操作(如TCP错误类型)执行回调,比如TCP连接时接收到ECONNREFUSED。
- idle, prepare:仅系统内部使用。
- 轮询(Poll )∶检索新的IO事件;执行与I/O相关的回调;
- 检测( check ) : setImmediate()回调函数在这里执行。
- 关闭的回调函数:一些关闭的回调函数,如:socket.on(‘close’,…)。
其中,Node程序经常停留在I/O阶段
例题
1 | Promise.resolve().then(() => { |
稍稍修改:
1 | Promise.resolve().then(() => { |
与第一次的不同之处在于,console.log(0)之后返回了一个thenable对象(实现了then方法的对象)
在原生的Promise的实现中,返回的thenable对象如果resolve了,这个resolve会被往后推一次,推到下一次的微任务里面
图示:
参看:
https://ke.qq.com/webcourse/3619571/103765593#taid=11926213651544819&vid=8602268011107553485
时间:1:10
再稍稍修改一下代码:
1 | Promise.resolve().then(() => { |
分析:返回了一个thenable对象会被退后一次,即:不是普通的值会被推后一次
如果返回了return Promise.resolve(4)
则会推后两次。