likes
comments
collection
share

node单线程如何做到非阻塞IO

作者站长头像
站长
· 阅读数 13

node架构

node单线程如何做到非阻塞IO

node单线程如何做到非阻塞IO

先说结论:可以理解为node是单线程的,但依赖的libuv是多线程、多进程的

Node.js本身是单线程的,但底层依赖的libuv库可以使用多线程或多进程。

Node.js的主线程是单线程的,也被称为事件循环线程。它负责处理事件循环、执行JavaScript代码以及调度异步操作。这个单线程是应用程序的入口点,负责处理请求和事件,并且对JavaScript代码进行解析和执行。

然而,libuv库是Node.js的核心库之一,它是跨平台的异步I/O库。libuv使用了多线程或多进程来执行异步操作,如文件操作、网络请求等。它可以创建额外的线程或进程来处理这些操作,以避免阻塞主事件循环线程。

通常情况下,当需要进行耗时的阻塞操作时,libuv会将这些操作委托给后台线程或进程来执行。后台线程或进程会独立处理这些操作,并在完成后通知主事件循环线程。这样,Node.js能够保持应用程序的响应性,同时利用多线程或多进程来提高系统资源的利用率。

总之,Node.js本身是单线程的,但通过libuv库的多线程或多进程机制,可以实现并发处理和异步操作,从而提供高性能和可伸缩性。

Node.js中的事件循环

node单线程如何做到非阻塞IO

事件循环机制(libuv执行),并通知到主线程(node的线程),告诉它我已经将完成事件添加到事件队列了,剩下的交给你了

然后,Node.js的主线程会从事件队列中获取完成事件,并触发事件循环机制中相应的回调函数或解析Promise,将结果返回给应用程序

Node.js中的事件循环(Event Loop)会为每个事件分配一个新的线程。以下是工作流程:

node单线程如何做到非阻塞IO

  1. 主线程执行完毕:主线程执行完毕后,并不会直接进入事件循环。它会等待事件循环启动并开始处理事件。
  2. 事件循环启动:当主线程等待时,事件循环会启动,并且开始监听事件队列中是否有新的事件。
  3. 取出事件:当事件循环监听到有新的事件时,它并不会为每个事件分配一个新的线程。而是按照事件队列中的顺序,依次取出一个事件。
  4. 执行事件回调:一旦事件被取出,事件循环会执行该事件对应的回调函数。这个回调函数通常是异步操作的完成回调,比如网络请求返回或文件读取完成等。
  5. 异步操作委托给底层库:在执行异步操作期间,Node.js会将这些操作委托给libuv库处理。libuv可以使用多线程或多进程来执行这些异步操作,但具体执行方式取决于操作系统和libuv的配置。
  6. 完成事件处理:一旦异步操作完成,libuv会通知主线程,并将完成事件添加到事件队列中。
  7. 回到事件循环:主线程在监听到完成事件后,会继续从事件队列中取出下一个事件,并执行对应的回调函数。

总结:Node.js的事件循环并不会为每个事件分配新的线程。它通过异步操作委托给底层库来处理,并在完成后通知主线程继续处理下一个事件。这样可以避免频繁地创建和销毁线程,并提高系统的性能和资源利用率。

事件循环的执行顺序

Event Loop(事件循环)是处理异步操作的核心机制。它负责执行和调度各种事件(如I/O操作、定时器等),并将回调函数推入适当的执行队列中。下面是Event Loop的大致执行顺序:

  1. 执行全局同步代码:首先,Node.js会执行全局的同步代码,包括模块加载、变量声明等。
  2. 执行当前轮次的微任务队列:在每个事件循环的开始阶段,Node.js会处理当前轮次的微任务队列(Promise的回调、process.nextTick等)。微任务会优先于宏任务执行。
  3. 执行当前轮次的宏任务队列:接下来,Node.js会处理当前轮次的宏任务队列,包括I/O事件、定时器等。宏任务包括定时器回调、网络I/O回调、文件I/O回调等。
  4. 检查是否需要进行下一轮事件循环:在执行完所有当前轮次的宏任务之后,Event Loop会检查是否还有待处理的微任务队列。如果有,将继续执行微任务队列中的回调函数;如果没有,开始下一轮事件循环。

注意事项:

  • Event Loop的执行顺序可能受到具体的执行环境和操作系统的影响。
  • process.nextTick()的回调函数会被优先执行,比Promise的回调函数更早。
  • 在每个阶段执行的回调函数数量是有限的,为了避免长时间占用事件循环,建议将耗时操作转移到子进程或线程池中处理。

优缺点

Nodejs 的优点:I/O 密集型处理是 Nodejs 的强项,因为 Nodejs 的 I/O 请求都是异步的(如:sql 查询请求、文件流操作操作请求、http 请求...)

Nodejs 的缺点:不擅长 cpu 密集型的操作(复杂的运算、图片的操作)

例如:常见的 CPU 密集型操作:

  1. 数据处理和转换:对大量的数据进行复杂的计算、清洗、转换或格式化,如数据分析、图像处理、音频/视频编解码等。
  2. 数值计算:执行复杂的数学运算,如矩阵计算、信号处理、模拟仿真等。
  3. 加密解密:进行加密算法(如AES、RSA)或哈希算法(如MD5、SHA)的计算,特别是在大规模的数据加密和解密场景下。
  4. 编译器:将高级语言代码转换为机器码的过程,包括词法分析、语法分析、优化以及生成目标代码等。
  5. 压缩和解压缩:对文件或数据进行压缩(如ZIP、Gzip)和解压缩操作。
  6. 图像和视频处理:包括图像滤波、图像增强、图像识别、视频编码等。
  7. 科学计算和仿真:在科学领域进行复杂模型的数值计算、仿真和建模。

总结

1、Nodejs 与操作系统交互,我们在 JavaScript 中调用的方法,最终都会通过 process.binding 传递到 C/C++ 层面,最终由他们来执行真正的操作。Node.js 即这样与操作系统进行互动。

2、Nodejs 所谓的单线程,只是主线程是单线程,所有的网络请求或者异步任务都交给了内部的线程池去实现,本身只负责不断的往返调度,由事件循环不断驱动事件执行。

3、Nodejs 之所以单线程可以处理高并发的原因,得益于 libuv 层的事件循环机制,和底层线程池实现。

4、Event loop 就是主线程从主线程的事件队列里面不停循环的读取事件,驱动了所有的异步回调函数的执行,Event loop 总共 7 个阶段,每个阶段都有一个任务队列,当所有阶段被顺序执行一次后,event loop 完成了一个 tick。

参考文章:

blog.csdn.net/ch834301/ar…

www.lsjlt.com/news/124518…