0

0

Node.js 内部结构

霞舞

霞舞

发布时间:2024-11-17 08:09:25

|

941人浏览过

|

来源于dev.to

转载

假设你去一家餐厅,有一位厨师承诺“我可以同时为数百人做饭,而你们不会挨饿”,听起来不可能,对吧?您可以将这个单一检查视为 node js,它管理所有这些多个订单,并且仍然为所有顾客提供食物。

每当你问某人“什么是 node js?”时,人们总是得到答案“node js 是一个运行时,用于在浏览器环境之外运行 javascript”。

但是,运行时是什么意思?...运行时环境是一种软件基础设施,其中代码执行被编写成特定的编程语言。它拥有运行代码、处理错误、管理内存以及与底层操作系统或硬件交互的所有工具、库和功能。

node js 拥有所有这些。

  • google v8 引擎来运行代码。

  • fs、crypto、http 等核心库和 api

  • libuv 和事件循环等基础设施支持异步和非阻塞 i/o 操作。

所以,我们现在可以知道为什么 node js 被称为运行时了。

此运行时由两个独立的依赖项组成,v8libuv.

v8 是 google chrome 中也使用的引擎,由 google 开发和管理。在 node js 中,它执行 javascript 代码。当我们运行命令 node index.js 时,node js 会将此代码传递给 v8 引擎。 v8 处理该代码、执行它并提供结果。例如,如果您的代码记录“hello, world!”对于控制台,v8 处理实现此操作的实际执行。

libuv 库包含 c++ 代码,当我们需要网络、i/o 操作或与时间相关的操作等功能时,可以使用该代码访问操作系统。它充当 node js 和操作系统之间的桥梁。

libuv 处理以下操作:

  • 文件系统操作:读取或写入文件(fs.readfile、fs.writefile)。

  • 网络:处理 http 请求、套接字或连接到服务器。

  • 计时器:管理 settimeout 或 setinterval 等函数。

文件读取等任务由 libuv 线程池处理,计时器由 libuv 的计时器系统处理,网络调用由操作系统级 api 处理。

node js 是单线程的吗?

看下面的例子。

const fs = require('fs');
const path = require('path');

const filepath = path.join(__dirname, 'file.txt');

const readfilewithtiming = (index) => {
  const start = date.now();
  fs.readfile(filepath, 'utf8', (err, data) => {
    if (err) {
      console.error(`error reading the file for task ${index}:`, err);
      return;
    }
    const end = date.now();
    console.log(`task ${index} completed in ${end - start}ms`);
  });
};

const startoverall = date.now();
for (let i = 1; i <= 4; i++) {
  readfilewithtiming(i);
}

process.on('exit', () => {
  const endoverall = date.now();
  console.log(`total execution time: ${endoverall - startoverall}ms`);
});

我们正在读取同一个文件四次,并且我们正在记录读取这些文件的时间。

我们得到此代码的以下输出。

task 1 completed in 50ms
task 2 completed in 51ms
task 3 completed in 52ms
task 4 completed in 53ms
total execution time: 54ms

我们可以看到我们几乎在第 50 毫秒完成了所有四个文件的读取。如果 node js 是单线程的话,那么这些文件读取操作是如何同时完成的呢?

这个问题回答了libuv库使用线程池。线程池是一堆线程。默认情况下,线程池大小为 4,意味着 libuv 可以一次处理 4 个请求。

考虑另一种情况,我们不是读取一个文件 4 次,而是读取该文件 6 次。

const fs = require('fs');
const path = require('path');

const filepath = path.join(__dirname, 'file.txt');

const readfilewithtiming = (index) => {
  const start = date.now();
  fs.readfile(filepath, 'utf8', (err, data) => {
    if (err) {
      console.error(`error reading the file for task ${index}:`, err);
      return;
    }
    const end = date.now();
    console.log(`task ${index} completed in ${end - start}ms`);
  });
};

const startoverall = date.now();
for (let i = 1; i <= 6; i++) {
  readfilewithtiming(i);
}

process.on('exit', () => {
  const endoverall = date.now();
  console.log(`total execution time: ${endoverall - startoverall}ms`);
});

输出将如下所示:

task 1 completed in 50ms
task 2 completed in 51ms
task 3 completed in 52ms
task 4 completed in 53ms
task 5 completed in 101ms
task 6 completed in 102ms
total execution time: 103ms

image description

假设读操作1和2完成并且线程1和2空闲。

Node.js 内部结构

您可以看到,前 4 次我们读取文件的时间几乎相同,但是当我们第 5 次和第 6 次读取该文件时,完成读取操作所需的时间几乎是前 4 次读取操作的两倍。

Akkio
Akkio

Akkio 是一个无代码 AI 的全包平台,任何人都可以在几分钟内构建和部署AI

下载

发生这种情况是因为线程池大小默认为 4,因此同时处理四个读取操作,但我们再次读取文件 2 次(第 5 次和第 6 次),然后 libuv 等待,因为所有线程都有一些工作。当四个线程之一完成执行时,将对该线程处理第 5 次读操作,并且将执行第 6 次读操作。这就是为什么需要更多时间的原因。

所以,node js 不是单线程的。

但是,为什么有些人将其称为单线程?

这是因为主事件循环是单线程的。该线程负责执行 node js 代码,包括处理异步回调和协调任务。它不直接处理文件 i/o 等阻塞操作。

代码执行流程是这样的。

  • 同步代码(v8):

node.js 使用 v8 javascript 引擎逐行执行所有同步(阻塞)代码。

  • 委派的异步任务:

诸如 fs.readfile、settimeout 或 http 请求之类的异步操作被发送到 libuv 库或其他子系统(例如操作系统)。

  • 任务执行:

文件读取等任务由 libuv 线程池处理,计时器由 libuv 的计时器系统处理,网络调用由操作系统级 api 处理。

  • 回调排队:

异步任务完成后,其关联的回调将被发送到事件循环的队列。

  • 事件循环执行回调:

事件循环从队列中获取回调并一一执行它们,确保非阻塞执行。

您可以使用 process.env.uv_threadpool_size = 8.

更改线程池大小

现在,我在想,如果我们设置大量的线程,那么我们也将能够处理大量的请求。我希望你能像我一样思考这个问题。

但是,这与我们的想法相反。

如果我们增加线程数量超过一定限制,那么它会减慢你的代码执行速度。

看下面的例子。

const fs = require('fs');
const path = require('path');

// set uv_threadpool_size to 100 (a high value) for this example
process.env.uv_threadpool_size = 100;

const filepath = path.join(__dirname, 'largefile.txt');

// function to simulate reading multiple files asynchronously
const readfilewithtiming = (index) => {
  const start = date.now();
  fs.readfile(filepath, 'utf8', (err, data) => {
    if (err) {
      console.error(`error reading the file for task ${index}:`, err);
      return;
    }
    const end = date.now();
    console.log(`task ${index} completed in ${end - start}ms`);
  });
};

const startoverall = date.now();
for (let i = 1; i <= 10; i++) {
  readfilewithtiming(i);
}

process.on('exit', () => {
  const endoverall = date.now();
  console.log(`total execution time: ${endoverall - startoverall}ms`);
});

输出:

具有高线程池大小(100 个线程)

task 1 completed in 100ms
task 2 completed in 98ms
task 3 completed in 105ms
task 4 completed in 95ms
task 5 completed in 120ms
task 6 completed in 130ms
task 7 completed in 135ms
task 8 completed in 140ms
task 9 completed in 125ms
task 10 completed in 150ms
total execution time: 700ms

现在,以下输出是当我们将线程池大小设置为 4(默认大小)时的输出。

使用默认线程池大小(4 个线程)

Task 1 completed in 100ms
Task 2 completed in 98ms
Task 3 completed in 105ms
Task 4 completed in 95ms
Task 5 completed in 100ms
Task 6 completed in 98ms
Task 7 completed in 102ms
Task 8 completed in 104ms
Task 9 completed in 106ms
Task 10 completed in 99ms
Total execution time: 600ms

可以看到总的执行时间有100ms的差异。总执行时间(线程池大小 4)为 600 毫秒,总执行时间(线程池大小 100)为 700 毫秒。因此,线程池大小为 4 花费的时间更少。

为什么线程数多!=可以同时处理更多任务?

第一个原因是每个线程都有自己的堆栈和资源需求。如果增加线程数量,最终会导致内存或 cpu 资源不足的情况。

第二个原因是操作系统必须调度线程。如果线程太多,操作系统将花费大量时间在线程之间切换(上下文切换),这会增加开销并降低性能,而不是提高性能。

现在,我们可以说,这不是增加线程池大小以实现可扩展性和高性能,而是使用正确的架构,例如集群,并了解任务性质(i/o 与 cpu 限制) )以及 node.js 的事件驱动模型如何工作。

感谢您的阅读。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
chrome什么意思
chrome什么意思

chrome是浏览器的意思,由Google开发的网络浏览器,它在2008年首次发布,并迅速成为全球最受欢迎的浏览器之一。本专题为大家提供chrome相关的文章、下载、课程内容,供大家免费下载体验。

865

2023.08.11

chrome无法加载插件怎么办
chrome无法加载插件怎么办

chrome无法加载插件可以通过检查插件是否已正确安装、禁用和启用插件、清除插件缓存、更新浏览器和插件、检查网络连接和尝试在隐身模式下加载插件方法解决。更多关于chrome相关问题,详情请看本专题下面的文章。php中文网欢迎大家前来学习。

756

2023.11.06

堆和栈的区别
堆和栈的区别

堆和栈的区别:1、内存分配方式不同;2、大小不同;3、数据访问方式不同;4、数据的生命周期。本专题为大家提供堆和栈的区别的相关的文章、下载、课程内容,供大家免费下载体验。

399

2023.07.18

堆和栈区别
堆和栈区别

堆(Heap)和栈(Stack)是计算机中两种常见的内存分配机制。它们在内存管理的方式、分配方式以及使用场景上有很大的区别。本文将详细介绍堆和栈的特点、区别以及各自的使用场景。php中文网给大家带来了相关的教程以及文章欢迎大家前来学习阅读。

577

2023.08.10

堆和栈的区别
堆和栈的区别

堆和栈的区别:1、内存分配方式不同;2、大小不同;3、数据访问方式不同;4、数据的生命周期。本专题为大家提供堆和栈的区别的相关的文章、下载、课程内容,供大家免费下载体验。

399

2023.07.18

堆和栈区别
堆和栈区别

堆(Heap)和栈(Stack)是计算机中两种常见的内存分配机制。它们在内存管理的方式、分配方式以及使用场景上有很大的区别。本文将详细介绍堆和栈的特点、区别以及各自的使用场景。php中文网给大家带来了相关的教程以及文章欢迎大家前来学习阅读。

577

2023.08.10

线程和进程的区别
线程和进程的区别

线程和进程的区别:线程是进程的一部分,用于实现并发和并行操作,而线程共享进程的资源,通信更方便快捷,切换开销较小。本专题为大家提供线程和进程区别相关的各种文章、以及下载和课程。

546

2023.08.10

js正则表达式
js正则表达式

php中文网为大家提供各种js正则表达式语法大全以及各种js正则表达式使用的方法,还有更多js正则表达式的相关文章、相关下载、相关课程,供大家免费下载体验。

516

2023.06.20

AO3官网入口与中文阅读设置 AO3网页版使用与访问
AO3官网入口与中文阅读设置 AO3网页版使用与访问

本专题围绕 Archive of Our Own(AO3)官网入口展开,系统整理 AO3 最新可用官网地址、网页版访问方式、正确打开链接的方法,并详细讲解 AO3 中文界面设置、阅读语言切换及基础使用流程,帮助用户稳定访问 AO3 官网,高效完成中文阅读与作品浏览。

24

2026.02.02

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Rust 教程
Rust 教程

共28课时 | 5.2万人学习

PostgreSQL 教程
PostgreSQL 教程

共48课时 | 8.3万人学习

Git 教程
Git 教程

共21课时 | 3.2万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号