深入浅出Node异步IO

异步IO

## 一、为什么要使用异步I/O

用户体验
javascript在单线程上执行,它与UI线程是一个线程,如果使用同步,当javascript在执行的时候UI渲染就必须要停止等待,这样就是的用户的体验极差。

如果网页需要请求一些资源,通过同步的方式获取的话,那么我们就必须要等待js从服务器完全获取到资源后再继续执行,这期间UI等待,就会使得与用户的交互极差,影响用户的体验感。

// 现在请求两个资源
//耗时为M毫秒
getData('from_db');
//耗时为N毫秒
getData('from_remote_api');

随着应用的复杂性,情景会变成M+N+…和Max(M,N,…),此时同步和异步的优劣就会更加凸显。另一方面,随着网站和应用的扩展,数据往往会分布到多台服务器上,而分布意味着M和N的值会线性增长,这也会放大异步和同步在性能上的差异。总之,IO是昂贵的,分布式IO是更昂贵的!

资源分配

单线程同步IO

会因阻塞IO使得硬件资源无法得到更优的利用。

多线程编程

优点: 可以利用多核CPU有效提升CPU的利用率
缺点: 编程中的死锁、状态同步使得程序员很是头疼。

node的异步IO

node采用的异步IO,利用单线程,远离了多线程死锁、状态同步,利用异步让单线程远离了阻塞,使得CPU得到更好的利用。
为了弥补单线程无法利用多核CPU的问题,Node提供了子进程 childProcess ,将一些运算多的任务放入子进程进行高效的运算。

在这里插入图片描述

二、阻塞I/O 和 非阻塞 I/O

阻塞IO

阻塞的IO操作就是发起IO操作后,线程阻塞等待IO完成,这期间cpu得不到有效利用。

在这里插入图片描述

非阻塞IO

非阻塞IO操作其实就是发起IO操作后,通过事件轮巡,或者事件通知机制,不断查询IO操作是否完成,或者是主线程进入休眠等待事件通知IO结束,然后继续向下执行代码,实际上非阻塞IO期间,cpu要不用来查询要不用来休眠,也没有得到有效利用。依旧是同步IO。

在这里插入图片描述

三、node的异步I/O

完成整个异步IO需要单个环节 事件循环 观察者 请求对象。

实际上node的异步IO是采用了线程池技术,发起异步IO时,把io操作扔到线程池里面执行,然后主线程继续执行其他操作,io执行完毕通过线程间通信通知主线程,主线程执行回调。
IO线程是由Libuv(Linux下由libeio具体实现;window下则由IOCP具体实现)管理的线程池控制的,本质上是多线程。即采用了线程池与阻塞IO模拟了异步IO。
在这里插入图片描述


异步IO原理

当遇到IO时,将其放入线程池中的一个IO线程,让该任务在IO线程上执行,在IO线程上是阻塞IO方式执行的,然后在主线程上继续执行,当遇到另一个IO任务时,将其在放入线程池,然后在另一条IO线程上执行(同样是阻塞IO方式),主线程继续执行着。

1、事件循环

正式因为事件循环使得回调函数非常的普遍。

  1. node进程启动的时候,会创建一个类似的 while(1) 循环,每执行依次循环的过程被称为是 Tick
  2. Tick过程就是查看当下是否有待处理的事件,如果有则取出相关事件以及回调函数,然后执行回调函数(在主线程中执行)。
  3. 然后进入下一个循环,继续检测如果不再有事件处理,就推出。
    当I/O线程上的任务(阻塞I/O)执行完毕之后,就会产生一个事件,这就是事件循环中的事件的产生由来。

在这里插入图片描述


2. 观察者

Node中事件的主要来源是网络请求和文件IO等。这些事件对应的观察者就是网络I/0观察者、文件I/0观察者。

3. 请求对象

在从js发起调用起到内核执行I/O完毕之后。这个中间过程有一种中间产物,称为是请求对象。

  • 以打开文件为例子
    在这里插入图片描述

0. 异步调用任务
1. js调用核心模块
2. 核心模块调用C++内建模块
3. 内建模块在`libuv`层,分平台处理。实质上调用的都是`uv_fs_open`方法。
4. 在调用的过程中,创建一个`FSReqWrap`请求对象。【这就是我们的主角请求对象了】
5. 对象创建完毕后,设置好参数和回调函数,就会将其推入线程池中等待执行了。
6. js线程继续执行后续的任务,当前的IO操作在线程池中执行,不管IO线程上是阻塞还是非阻塞,都不会影响主线程的执行,因此这就达到了异步的目的了。
  • 到这里其实就完成了异步IO的第一步了,回调通知则是第二步。

4. 执行回调

当IO线程中的任务执行完毕后,就会将执行结果放在请求对象中。然后通知TOCP。TOCP检查任务是否完成。如果完成了就将I/O请求对象加入观察者队列中,当作事件处理。然后通过事件循环来执行回调函数。
在这里插入图片描述

  • 注意: Windows下是TOCP ,Linux下是通过epoll。

四、 node的非I/O的异步API

1. 定时器

定时器的实现原理同异步IO,只是没有使用线程池。

setTimeout()

每次Tick,都会从该红黑树迭代取出定时器对象,然后检查是否超过了时间,如果超时那么就形成一个事件,回调函数执行。

2. process.nextTick()

立即执行一个异步任务

我们之前可能都这么做
setTimeout(function(){
//
}, 0);

使用上面的方法浪费性能,使用process.nextTick()则更为轻量。
process.nextTick(function(){
//
})

  • 区别

// 原始
A();
B();
C();

在这里插入图片描述

A();
process.nextTick(B);
C();

在这里插入图片描述

A();
setImmediate(B);//或者setTimeout(B,0);
C();

在这里插入图片描述
收录:原文地址

相遇就是缘分,iOS学习交流群:642363427,不管你是大牛还是小白都欢迎入驻 ,分享BAT,阿里面试题、面试经验,讨论技术, 大家一起交流学习成长!

还有更多福利:

  • 点击加入iOS学习交流群

  • BAT大厂面试题、独家面试工具包,

  • 资料免费领取,包括阿里面试题 ,iso高级面试题,大厂面试题,BAT面试题。
    在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值