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)会为每个事件分配一个新的线程。以下是工作流程:
- 主线程执行完毕:主线程执行完毕后,并不会直接进入事件循环。它会等待事件循环启动并开始处理事件。
- 事件循环启动:当主线程等待时,事件循环会启动,并且开始监听事件队列中是否有新的事件。
- 取出事件:当事件循环监听到有新的事件时,它并不会为每个事件分配一个新的线程。而是按照事件队列中的顺序,依次取出一个事件。
- 执行事件回调:一旦事件被取出,事件循环会执行该事件对应的回调函数。这个回调函数通常是异步操作的完成回调,比如网络请求返回或文件读取完成等。
- 异步操作委托给底层库:在执行异步操作期间,Node.js会将这些操作委托给libuv库处理。libuv可以使用多线程或多进程来执行这些异步操作,但具体执行方式取决于操作系统和libuv的配置。
- 完成事件处理:一旦异步操作完成,libuv会通知主线程,并将完成事件添加到事件队列中。
- 回到事件循环:主线程在监听到完成事件后,会继续从事件队列中取出下一个事件,并执行对应的回调函数。
总结:Node.js的事件循环并不会为每个事件分配新的线程。它通过异步操作委托给底层库来处理,并在完成后通知主线程继续处理下一个事件。这样可以避免频繁地创建和销毁线程,并提高系统的性能和资源利用率。
事件循环的执行顺序
Event Loop(事件循环)是处理异步操作的核心机制。它负责执行和调度各种事件(如I/O操作、定时器等),并将回调函数推入适当的执行队列中。下面是Event Loop的大致执行顺序:
- 执行全局同步代码:首先,Node.js会执行全局的同步代码,包括模块加载、变量声明等。
- 执行当前轮次的微任务队列:在每个事件循环的开始阶段,Node.js会处理当前轮次的微任务队列(Promise的回调、process.nextTick等)。微任务会优先于宏任务执行。
- 执行当前轮次的宏任务队列:接下来,Node.js会处理当前轮次的宏任务队列,包括I/O事件、定时器等。宏任务包括定时器回调、网络I/O回调、文件I/O回调等。
- 检查是否需要进行下一轮事件循环:在执行完所有当前轮次的宏任务之后,Event Loop会检查是否还有待处理的微任务队列。如果有,将继续执行微任务队列中的回调函数;如果没有,开始下一轮事件循环。
注意事项:
- Event Loop的执行顺序可能受到具体的执行环境和操作系统的影响。
- process.nextTick()的回调函数会被优先执行,比Promise的回调函数更早。
- 在每个阶段执行的回调函数数量是有限的,为了避免长时间占用事件循环,建议将耗时操作转移到子进程或线程池中处理。
优缺点
Nodejs 的优点:I/O 密集型处理是 Nodejs 的强项,因为 Nodejs 的 I/O 请求都是异步的(如:sql 查询请求、文件流操作操作请求、http 请求...)
Nodejs 的缺点:不擅长 cpu 密集型的操作(复杂的运算、图片的操作)
例如:常见的 CPU 密集型操作:
- 数据处理和转换:对大量的数据进行复杂的计算、清洗、转换或格式化,如数据分析、图像处理、音频/视频编解码等。
- 数值计算:执行复杂的数学运算,如矩阵计算、信号处理、模拟仿真等。
- 加密解密:进行加密算法(如AES、RSA)或哈希算法(如MD5、SHA)的计算,特别是在大规模的数据加密和解密场景下。
- 编译器:将高级语言代码转换为机器码的过程,包括词法分析、语法分析、优化以及生成目标代码等。
- 压缩和解压缩:对文件或数据进行压缩(如ZIP、Gzip)和解压缩操作。
- 图像和视频处理:包括图像滤波、图像增强、图像识别、视频编码等。
- 科学计算和仿真:在科学领域进行复杂模型的数值计算、仿真和建模。
总结
1、Nodejs 与操作系统交互,我们在 JavaScript 中调用的方法,最终都会通过 process.binding 传递到 C/C++ 层面,最终由他们来执行真正的操作。Node.js 即这样与操作系统进行互动。
2、Nodejs 所谓的单线程,只是主线程是单线程,所有的网络请求或者异步任务都交给了内部的线程池去实现,本身只负责不断的往返调度,由事件循环不断驱动事件执行。
3、Nodejs 之所以单线程可以处理高并发的原因,得益于 libuv 层的事件循环机制,和底层线程池实现。
4、Event loop 就是主线程从主线程的事件队列里面不停循环的读取事件,驱动了所有的异步回调函数的执行,Event loop 总共 7 个阶段,每个阶段都有一个任务队列,当所有阶段被顺序执行一次后,event loop 完成了一个 tick。
参考文章: