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

node架构

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中的事件循环

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

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

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

  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...

相关推荐
Lee川2 小时前
从异步迷雾到优雅流程:JavaScript异步编程与内存管理的现代化之旅
javascript·面试
晴殇i4 小时前
揭秘JavaScript中那些“不冒泡”的DOM事件
前端·javascript·面试
绝无仅有4 小时前
Redis过期删除与内存淘汰策略详解
后端·面试·架构
绝无仅有4 小时前
Redis大Key问题排查与解决方案全解析
后端·面试·架构
AAA梅狸猫5 小时前
Looper.loop() 循环机制
面试
AAA梅狸猫5 小时前
Handler基本概念
面试
码路飞5 小时前
Node.js 中间层我维护了两年,这周终于摊牌了——成本账单算完我人傻了
node.js
Wect6 小时前
浏览器缓存机制
前端·面试·浏览器
掘金安东尼6 小时前
Fun with TypeScript Generics:玩转 TS 泛型
前端·javascript·面试
掘金安东尼7 小时前
Next.js 企业级落地
前端·javascript·面试