js单线程
- 执行js业务逻辑的只有一个线程
- 网络、io等由其他线程完成
异步 I/O 操作
事件循环 ,观察者 ,请求对象 ,线程池 构成了整个异步 I/O 执行模型。
它是生产消费者模式
-
事件循环 node启动时会创建一个类似while循环,每次循环称为tick,每个tick就是向观察者询问有没有该执行的任务,如果有,那么观察者会取出任务,交给事件循环去执行。
-
回调事件队列:事件循环将回调任务放入事件队列wq,唤醒js进行执行
-
观察者 主要是保存了io相关的文件描述符、回调、感兴趣的事件等信息。
-
watcher queue观察者队列:所有需要libuv处理的io观察者都挂载在这个队列里。libuv会逐个处理。。
-
请求对象 比如调用
fs.readFile,本质上调用libuv上的方法创建一个请求对象。这个请求对象上保留着此次 I/O 请求的信息,包括此次 I/O 的主体和回调函数等。然后异步调用的第一阶段就完成了,JavaScript 会继续往下执行执行栈上的代码逻辑,当前的 I/O 操作将以请求对象的形式放入任务队列中,等待执行。 -
线程池 Nodejs 的线程池在 Windows 下有内核( IOCP )提供,在 Unix 系统中由
libuv自行实现, 线程池用来执行部分的 I/O (系统文件的操作),线程池大小默认为 4 ,多个文件系统操作的请求可能阻塞到一个线程中。 -
任务队列: 一次异步 I/O 会把请求对象放在任务队列中,首先会判断当前线程池是否有可用的线程,如果线程可用,那么会执行任务队列请求对象的 I/O 操作。已经完成的 I/O 对象提交到I/O 观察者列队。
-
第一阶段:每一次异步 I/O 的调用,首先在 nodejs 底层设置请求参数和回调函 callback,形成请求对象。
-
第二阶段:形成的请求对象,会被放入线程池任务队列,如果线程池有空闲的 I/O 线程,会执行此次 I/O 任务,得到结果。
-
第三阶段:事件循环中 I/O 观察者,会从请观察者中找到已经得到结果的 I/O 请求对象,取出结果和回调函数,将回调函数放入事件循环中,执行回调,完成整个异步 I/O 任务
fs与网络io机制
-
Network I/O:最后的调用都会归结到
uv__io_start这个函数,而该函数会将需要执行的I/O事件和回调塞到watcher队列中,之后uv_run函数执行的Poll for I/O阶段做的便是从watcher队列中取出事件调用系统的接口,这是其中一条主线; -
node通过事件驱动的方式处理请求:每个请求形成事件交给io观察者,事件循环不停处理i/o事件,如果有回调则传回js处理。
-
Fs I/O和DNS的所有操作都会归结到调用
uv__work_sumit这个函数,而该函数就是执行线程池初始化并调度的终极函数。这是另外一条主线。
异步非io
这些不需要线程池,会插入到watcher中
- setTimeout
- setInterval
- setImmediate
- process.nextTick
事件循环
在处理完一个阶段后,移向下一个阶段之前,事件循环将会处理两个中间队列,直到两个中间队列为空
- 第一阶段:
timer,timer 阶段主要做的事是,执行setTimeout或setInterval注册的回调函数。 - 第二阶段:pending callback ,大部分 I/O 回调任务都是在 poll 阶段执行的,但是也会存在一些上一次事件循环遗留的被延时的 I/O 回调函数,那么此阶段就是为了调用之前事件循环延迟执行的 I/O 回调函数。
- 第三阶段:idle prepare 阶段,仅用于 nodejs 内部模块的使用。
- 第四阶段:poll 轮询阶段,这个阶段主要做两件事,一这个阶段会执行异步 I/O 的回调函数; 二 计算当前轮询阶段阻塞后续阶段的时间。
poll io 是nodejs非常重要的一个阶段,
文件io、网络io、信号处理等都在这个阶段处理,这也是最复杂的一个阶段 - 第五阶段:check阶段,当 poll 阶段回调函数队列为空的时候,开始进入 check 阶段,主要执行
setImmediate回调函数。 - 第六阶段:close阶段,执行注册
close事件的回调函数。
欢迎关注我的前端自检清单,我和你一起成长