Распространенное заблуждение, что Node.js — чисто однопоточный, не соответствует действительности. Разберем архитектуру Node.js чтобы доказать его многопоточную природу.
Хотя JavaScript-код выполняется в одном потоке Event Loop'а, система включает множество других потоков:
const { threadId } = require('worker_threads');
console.log(`Main thread ID: ${threadId}`);
Node.js использует libuv, который по умолчанию создает 4 дополнительных потока для операций:
Проверим количество потоков:
$ ps -M <pid_node_process>
Многие встроенные модули используют отдельные потоки:
crypto.pbkdf2
fs.readFile
zlib.gzip
Пример:
const crypto = require('crypto');
// Выполняется в отдельном потоке пула
crypto.pbkdf2('secret', 'salt', 100000, 64, 'sha512', () => {});
С версии 10.5.0 Node.js предоставляет настоящую многопоточность:
const { Worker } = require('worker_threads');
new Worker(`console.log('Thread ID: ${threadId}')`, { eval: true });
Event Loop — это концепция управления задачами, а не поток. Он координирует работу:
Сердце Node.js использует:
Запустим CPU-intensive задачи:
const start = Date.now();
// Главный поток
setTimeout(() => console.log(`Main: ${Date.now() - start}ms`), 100);
// Блокирующая операция в потоке пула
const crypto = require('crypto');
crypto.pbkdf2('a', 'b', 100000, 512, 'sha512', () => {
console.log(`Crypto: ${Date.now() - start}ms`);
});
Обе задачи завершатся примерно одновременно, что доказывает параллельное выполнение.
Node.js никогда не был чисто однопоточным:
Архитектура Node.js — это грамотная комбинация однопоточного Event Loop'а и многопоточной подсистемы ввода-вывода, что дает лучшее из обоих миров.