异步IO
Node基调=异步IO(事件循环+IO线程池)+单线程+事件驱动
一.为什么要异步IO
Node面向网络设计(跨网络),导致并发是标配。具体从用户体验和资源分配展开:
1. 用户体验
前端浏览器 JS执行与UI渲染共用一个线程,因此JS的阻塞(同步请求)会导致UI渲染阻塞(用户交互体验差)。
前端通过异步请求解决这个问题。
在后端,由于数据分布式已成常态,因此异步IO可以有效地提高资源请求速度,也因此可以更快的响应前端请求,提高用户体验。
2. 资源分配
多线程能够提高CPU使用率,但会带来死锁状态同步线程创建和切换开销等问题。同步IO会阻塞硬件资源。
基于以上两点,单线程异步IO成了最优解(通过子进程提高CPU使用率)。
二.异步IO实现现状
1. 异步IO与非阻塞IO
异步IO看起来等同于非阻塞IO,但从计算机系统内核的角度而言,只认阻塞/非阻塞IO,将其提供给应用程序(调用方)。
-
阻塞IO特点是++一定需要在系统内核层面完成所有操作,调用才完成++ -
非阻塞IO特点是 ++不带数据直接返回,但想要获取数据,还得轮询++
2. 理想的非阻塞异步IO
...
3. 现实异步IO
要实现理想的异步IO,需要使用线程池来模拟异步IO。
三.Node的异步IO
Node异步IO的实现=事件循环(Node的执行模型)+观察者+请求对象
1. 事件循环
Node进程开启后,会执行一个类似while(true)的循环(Tick),循环体是:
- Node进程开始,检查是否有事件待处理
- 有事件:取出事件,检查是否有关联回调
- 有回调:执行回调
- 无回调:本轮循环结束,开始下一循环
- 无事件:结束Node进程
- 有事件:取出事件,检查是否有关联回调
2. 观察者
- Node进程通过
观察者来判断是否有事件需要处理:Node在每轮Tick,都会询问观察者是否有事件待处理。 观察者可以有多个- 一个
观察者可以对应多个事件,++观察者将事件分类++。
3. 请求对象
请求对象是 JS执行异步IO调用到 到 回调函数执行 这一过程中重要的中间产物。
异步IO第一阶段
- 发起异步IO调用
- JS调用
fs核心模块 核心模块调用C++内建模块libuv进行平台判断- 调用
uv_fs_open()
- JS调用
uv_fs_open函数中包装请求对象
JS调用时传入的参数和回调都会被作为属性添加到请求对象FSReqWrap中。- 将请求对象推入线程池
4. 执行回调
异步IO第二阶段
- IO操作执行完成后,将结果添加到请求对象
result属性,并丢给IO观察者 事件循环通过IO观察者获取到请求对象,作为事件处理。
5. 小结
四.非IO的异步API
1. 定时器
JS调用的定时器将被插入到定时器观察者的红黑树中,每次Tick都会检查下是否超过时间,如果超过,就形成一个事件,回调函数立即执行。
2. process.nextTick()
相较于创建定时器对象,process.nextTick()更加轻量,它只会将++回调放入队列++,在下一次Tick时取出执行。
3. setImmediate()
setImmediate()效果看起来与process.nextTick()一样。两者区别:
- 优先级不同:
setImmediate()属于check观察者,process.nextTick()属于idle观察者,事件循环对于两种观察者的检查优先级不同:++idle观察者>IO观察者>check观察者++ - 实现不同:
process.nextTick()将回调保存在一个数组,setImmediate()将回调保存在链表中。每轮Tick,数组中的回调会全部执行完,而链表中的回调每轮只会执行一个。
// 加入两个nextTick()的回调函数
process.nextTick(function () {
console.log('nextTick延迟执行1');
});
process.nextTick(function () {
console.log('nextTick延迟执行2');
});
// 加入两个setImmediate()的回调函数
setImmediate(function () {
console.log('setImmediate延迟执行1');
// 进入下次循环
process.nextTick(function () {
console.log('强势插入');
});
});
setImmediate(function () {
console.log('setImmediate延迟执行2');
});
console.log('正常执行');
正常执行
nextTick延迟执行1
nextTick延迟执行2
setImmediate延迟执行1
强势插入
setImmediate延迟执行2
五.事件驱动与高性能服务器
事件驱动的实质是++主循环+事件触发++
IO观察者的事件回调,理解为接收请求这一事件的回调。
六.总结
事件循环是异步实现的核心,与浏览器端的执行模型基本保持一致。