学习Node就绕不开异步IO, 异步IO又与事件循环息息相关, 而关于这一块一直没有仔细去了解整理过, 刚好最近在做项目的时候, 有了一些思考就记录了下来, 希望能尽量将这一块的知识整理清楚, 如有错误, 请指点轻喷~~
一些概念
同步异步 & 阻塞非阻塞
查阅资料的时候, 发现很多人都对 异步和非阻塞 的概念有点混淆, 其实两者是完全不同的, 同步异步指的是 行为即两者之间的关系 , 而阻塞非阻塞指的是 状态即某一方 。
以前端请求为一个例子,下面的代码很多人都应该写过
$.ajax(url).succedd(() => { ...... // to do something })
同步异步
如果是同步的话, 那么应该是client发起请求后, 一直等到serve处理请求完成后才返回继续执行后续的逻辑, 这样 client和serve之间就保持了同步的状态 。
如果是异步的话, 那么应该是client发起请求后, 立即返回 , 而请求可能还没有到达server端或者请求正在处理, 当然在异步情况下, client端通常会注册事件来处理请求完成后的情况, 如上面的succeed函数。
阻塞非阻塞
首先需要明白一个概念, Js是单线程, 但是浏览器并不是, 事实上你的请求是浏览器的另一个线程在跑。
如果是阻塞的话, 那么 该线程就会一直等到这个请求完成之后才能被释放用于其他请求 。
如果是非阻塞的话, 那么 该线程就可以发起请求后而不用等请求完成继续做其他事情 。
总结
之所以经常会混乱是因为没有说清楚讨论的是哪一部分(下面会提到), 所以 同步异步讨论的对象是双方, 而阻塞非阻塞讨论的对象是自身 。
IO和CPU
Io和Cpu是可以同时进行工作的 。
IO:
I/O(英语:Input/Output),即输入/输出,通常指数据在内部存储器和外部存储器或其他周边设备之间的输入和输出。
cpu
解释计算机指令以及处理计算机软件中的数据。
Node中的异步IO模型
IO分为 磁盘IO和网络IO , 其具有两个步骤
等待数据准备 (Waiting for the data to be ready)
将数据从内核拷贝到进程中 (Copying the data from the kernel to the process)
Node中的磁盘Io
以下的讨论基于*nix系统。
理想的异步Io应该像上面讨论的一样, 如图:
而实际上, 我们的系统并不能完美的实现这样的一种调用方式, Node的异步IO, 如读取文件等采用的是线程池的方式来实现, 可以看到, Node通过另外一个线程来进行Io操作, 完成后再通知主线程:
而在window下, 则是利用 IOCP 接口来完成, IOCP从用户的角度来说确实是完美的异步调用方式, 而实际也是利用内核中的线程池, 其与nix系统的不同在于后者的线程池是用户层提供的线程池。
Node中的网络Io
在进入主题之前, 我们先了解下Linux的Io模式, 这里推荐大家看这篇文章, 大致总结如下:
阻塞 I/O(blocking IO)
所以,blocking IO的特点就是在IO执行的两个阶段都被block了。
非阻塞 I/O(nonblocking IO)
当用户进程发出read操作时,如果kernel中的数据还没有准备好,那么它并不会block用户进程,而是立刻返回一个error。从用户进程角度讲 ,它发起一个read操作后,并不需要等待,而是马上就得到了一个结果。用户进程判断结果是一个error时,它就知道数据还没有准备好,于是它可以再次发送read操作。一旦kernel中的数据准备好了,并且又再次收到了用户进程的system call,那么它马上就将数据拷贝到了用户内存,然后返回。
I/O 多路复用( IO multiplexing)
所以,I/O 多路复用的特点是通过一种机制一个进程能同时等待多个文件描述符,而这些文件描述符(套接字描述符)其中的任意一个进入读就绪状态,select()函数就可以返回。
异步 I/O(asynchronous IO)