- Introdução
- Como o Node trata o Código Assíncrono
- Operações Assíncronas: O que São?
- Experimentos com Funções Bloqueantes
- Experimentos com Funções Não-Bloqueantes
- Operações Assíncronas Não Bloqueantes e SO
- Conclusão
介绍
我最近研究了node.js.
的执行我最终从有关como o Event Loop funciona的文章中进行了很多学习(和写作),©twitter上的线程解释了quem espera a requisição http terminar。
如果您愿意,也可以通过单击aqui
来访问我在编写此帖子之前创建的心理图。现在,让我们去开展业务!
节点如何治疗牛
无节点:
- 所有JavaScript均在主线程上执行。
- 图书馆 libuv >
- 按模式,libuv提供4个 worker threads node.js
- 仅在突击操作阻止时才使用这些线程,在这种情况下,将libuv线程之一(是操作系统线程)从主线程(节点执行)中锁定。
- 有阻塞和阻止操作,当前大多数突击行动都在阻止。
Operaind Assancronas:您£o?
通常,在异步操作方面存在混乱。
许多人认为这意味着背景中,同时或其他线程发生了某些事情。
实际上,突击操作是现在不会返回的操作,而是。
它们取决于与外部代理的通信,这些代理可能不会立即对其请求做出响应。
我们正在谈论I/O操作(输入/输出)。
示例: p>
- 文件读数:磁盘的数据数据在应用程序中的 enter 。
- 在文件中写的:data explying ,输入磁盘。
-
Operações de Rede
- http申请,例如。
- 应用程序将 a 申请http 发送到某些服务器,接收数据。
阻止可分配的操作没有阻止
在现代世界中,人们不会说话大多数被掩盖的操作都不会阻止。
但是,这意味着:
- libuv提供4个线程( by pattest )。
- 他们“照顾” I/O操作阻止。
- 绝大多数行动。
似乎有点不眠吗?
考虑到这个质疑,我决定进行一些实验。
comfunã§封锁器
首先,我测试了CPU的强烈CPU使用,CPU是罕见函数之一,阻止了node的分配
使用的方式如下:
// index.js
// index.js
import { pbkdf2 } from "crypto";
const TEN_MILLIONS = 1e7;
// Função assíncrona de uso intenso de CPU
// Objetivo: Bloquear uma worker thread
// Objetivo original: Gerar uma palavra-chave
// O terceiro parâmetro é o número de iterações
// Nesse exemplo, estamos passando 10 milhões
function runSlowCryptoFunction(callback) {
pbkdf2("secret", "salt", TEN_MILLIONS, 64, "sha512", callback);
}
// Aqui queremos saber quantas workers threads a libuv vai usar
console.log(`Thread pool size is ${process.env.UV_THREADPOOL_SIZE}`);
const runAsyncBlockingOperations = () => {
const startDate = new Date();
const runAsyncBlockingOperation = (runIndex) => {
runSlowCryptoFunction(() => {
const ms = new Date() - startDate;
console.log(`Finished run ${runIndex} in ${ms/1000}s`);
});
}
runAsyncBlockingOperation(1);
runAsyncBlockingOperation(2);
};
runAsyncBlockingOperations();
要验证操作,我包围了命令:
UV_THREADPOOL_SIZE=1 node index.js
重要:
- uv_threadpool_size:â确定libuv节点的 worker线程的差异。
结果是:
Thread pool size is 1
Finished run 1 in 3.063s
Finished run 2 in 6.094s
也就是说,使用1stnica螺纹,每个执行时间耗时约6秒,并且它们顺序发生。一个ap。
现在,我决定进行以下测试:
UV_THREADPOOL_SIZE=2 node index.js
结果如下:
Thread pool size is 2
Finished run 2 in 3.225s
Finished run 1 in 3.243s
这样,证明了libuv的 worker线程,在Node.js中处理阻止Asastial操作。
但是阻止 呢?如果没有人在等他们,它们如何工作?
我决定编写另一个功能进行测试。
具有非块功能的实验
futch futch (节点本地)执行网络Ashnial操作,它是一个块。
以以下方式,我数据金进行了第一个实验的测试:
//non-blocking.js
// Aqui queremos saber quantas workers threads a libuv vai usar
console.log(`Thread pool size is ${process.env.UV_THREADPOOL_SIZE}`);
const startDate = new Date();
fetch("https://www.google.com").then(() => {
const ms = new Date() - startDate;
console.log(`Fetch 1 retornou em ${ms / 1000}s`);
});
fetch("https://www.google.com").then(() => {
const ms = new Date() - startDate;
console.log(`Fetch 2 retornou em ${ms / 1000}s`);
});
并使用以下命令执行脚本:
UV_THREADPOOL_SIZE=1 node non-blocking.js
结果如下:
Thread pool size is 1
Fetch 1 retornou em 0.391s
Fetch 2 retornou em 0.396s
入口,我决定使用两个线程进行测试,以查看是否发生了变化:
UV_THREADPOOL_SIZE=2 node non-blocking.js
和入口:
Thread pool size is 2
Fetch 2 retornou em 0.402s
Fetch 1 retornou em 0.407s
这样,我可以看到:
在libuv中运行更多线程无助于执行非块分配操作。
但是,我再次想知道,如果没有Libuv线程“等待”返回的请求,它是如何工作的?
我的朋友,这是我在一项巨大的研究和有关操作的知识中
的知识。攻击行动不阻止
多年来,操作系统已经发展了很多,以便能够以非封锁方式处理I/O操作,这是通过 syscalls ,sa o es进行的:
- 选择/民意调查:这些是处理I/O封锁的传统方式,通常被认为效率较低。
- iocp :在Windows中用于Asanchronous操作。 Kqueue:Macos和BSD的母亲
- epoll :有效且用于Linux。相反,他不受FD的数量的限制。
- io_uring :epoll进化,带来了绩效的改进和基于队列的方法。
要更好地理解,我们需要深入研究I/O不封锁的操作细节。
ENTENDONO文件描述符
为了解释I/O不封锁,我需要快速解释文件描述符(SDS)的概念。
什么是FD?
â内核维护的许多表,其中每个记录都有:
- 功能类型(例如文件,套接字,设备)。
- 文件指针的当前位置。
- 权限和标志,定义诸如阅读或写作之类的模式。
- coferentiaã资源数据结构中的内核。
它们对于I/O管理是基础的。
FD和I/O不封锁
通过启动I/O操作不阻止,Linux将FD链接到它而不会中断(阻止)过程的执行。
例如:
让我们想象您要读取包含一个非常大的文件的内容。
阻止方法:
- process调用函数读取文件
- 流程正在等待阅读文件的控制
- 只要结束结束,该过程就会被阻止
方法不阻止:
- 流程请求阅读 assoncrona 。
- 因此,您开始阅读遏制并将FD返回到该过程中。
- 过程没有锁定,可以做其他事情。
- 时不时地调用 syscall 知道阅读是否结束。
定义阅读方式的是过程,该过程是通过fcntl函数使用flag o_nonblock ,但是目前是次要的。
用Syscall监视FD
为了有效地观察母亲FD,SOS具有一些 syscalls :
理解选择:
- 收到FDS列表。
- 阻止了一个或多个FD为指定操作准备的过程(阅读,书面,例外)。
- SYSCALL返回后,该程序可以在SD上的项目中识别为I/O。 准备的程序。
- 使用(n)的搜索算法。
- 效率低下,缓慢,疲倦了许多FD
epoll
这是 select < /em>的演变,它使用自平衡裁决来存储FD,使访问时间实际上是constance,(1)。 /p>
太别致! 如何工作: 这个家伙来这里踢帐篷棒。 尽管 epoll 已经发展(很多!)搜索性能和seizure fds
epoll_create
创建的。epoll_ctl
相关联。epoll_wait
等待任何FDS。
io_uring
所以,在理解它的工作原理之后,我想知道没有人在以前想知道它!
回顾:
- 选择:接收一个FDS列表,顺序存储(作为数组),然后检查1到1(复杂性O(n))以查看谁有变化或活动。li> li>
- epoll :接收一个FDS列表,使用自脚的self stolach储存它们,看不到1到1,更有效,并且与选择相同,只是具有复杂性o(1)
从历史上看,该过程负责迭代返回的FD,以找出谁结束了。
- io_uring :怎么样?返回列表?进行投票?愚蠢?您听说过队列?
它使用两条主要线路起作用,以矮的形式©is(戒指,名称io- ring )。
- 1提交任务
- 1 tarefas完成
simples否?
启动I/O操作时的过程, lump 使用 ion_urn_uring 。
的操作aã,在呼叫 select 或 epoll 的发明中,并且在返回的fds中,它是在每个人上的零件中,可以选择通知该过程当某些I/O操作结束时。
包括£o
这样,现在我确切地知道节点通过哪种方式执行突击操作。
正在阻止:
- 使用libuv 执行ASANCRONE操作
- 添加到libuv 工作线程
- a 工作线程被阻止,等待完成操作。
- 最后,该线程负责将结果放在事件循环
- 在主线程上执行回调
没有阻止:
- 使用libuv 执行ASANCRONE操作
- libuv执行I/O的I/O的A syscall 不阻止
- do 用FDS解析(epoll) 的投票
- 来自版本20.3.0使用io_uring
- 顺从行/完成操作的方法
- 收到完成的操作事件后
- Libuv负责执行或回调NA线程Main