Почему node.js не однопоточный? Докажите, что даже не был однопоточным.nodejs-3

Распространенное заблуждение, что Node.js — чисто однопоточный, не соответствует действительности. Разберем архитектуру Node.js чтобы доказать его многопоточную природу.

1. Event Loop — только верхушка айсберга

Хотя JavaScript-код выполняется в одном потоке Event Loop'а, система включает множество других потоков:

const { threadId } = require('worker_threads');
console.log(`Main thread ID: ${threadId}`);

2. Доказательства многопоточности

a) Пул потоков libuv

Node.js использует libuv, который по умолчанию создает 4 дополнительных потока для операций:

  • FS I/O
  • DNS
  • Crypto
  • Zlib

Проверим количество потоков:

$ ps -M <pid_node_process>

b) Нативные модули

Многие встроенные модули используют отдельные потоки:

  • crypto.pbkdf2
  • fs.readFile
  • zlib.gzip

Пример:

const crypto = require('crypto');
// Выполняется в отдельном потоке пула
crypto.pbkdf2('secret', 'salt', 100000, 64, 'sha512', () => {});

c) Worker Threads

С версии 10.5.0 Node.js предоставляет настоящую многопоточность:

const { Worker } = require('worker_threads');
new Worker(`console.log('Thread ID: ${threadId}')`, { eval: true });

3. Внутренние механизмы

a) Цикл событий ≠ поток

Event Loop — это концепция управления задачами, а не поток. Он координирует работу:

  • Главного потока V8
  • Потоков libuv
  • Потоков операционной системы

b) Потоки в ядре Node.js

Сердце Node.js использует:

  • 1 основной поток JavaScript
  • 1 поток для дебаггера (если включен)
  • 1 поток для сборщика мусора
  • Потоки libuv (по умолчанию 4)
  • Потоки для задач операционной системы

4. Практическая демонстрация

Запустим 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 никогда не был чисто однопоточным:

  1. Event Loop — однопоточный только для JavaScript-кода
  2. libuv предоставляет пул из 4+ потоков
  3. Нативные модули используют отдельные потоки
  4. Worker Threads позволяют создавать дополнительные потоки
  5. Системные вызовы выполняются в потоках ОС

Архитектура Node.js — это грамотная комбинация однопоточного Event Loop'а и многопоточной подсистемы ввода-вывода, что дает лучшее из обоих миров.