Проверяет понимание модели исполнения Node.js и того, как однопоточный event loop сочетается с многопоточными возможностями ОС.
Node.js часто называют однопоточной средой, и это верно для уровня JavaScript: весь пользовательский код, включая колбэки, промисы и обработчики событий, выполняется в одном главном потоке. Именно поэтому в Node.js нет гонок данных между JS-функциями и не нужны мьютексы для обычных переменных. Однако это лишь верхний слой архитектуры.
За асинхронность отвечает библиотека libuv. Она предоставляет event loop и пул рабочих потоков (thread pool), размер которого по умолчанию равен 4 (настраивается через переменную окружения UV_THREADPOOL_SIZE). Этот пул используется для операций, которые нельзя выполнить неблокирующе на уровне ОС:
Сетевой ввод-вывод (TCP, UDP, HTTP) обрабатывается иначе: libuv использует системные механизмы мультиплексирования epoll (Linux), kqueue (macOS) или IOCP (Windows), поэтому для тысяч соединений не требуется отдельный поток на каждое.
const crypto = require('crypto');
// Синхронная операция блокирует event loop
console.time('sync');
crypto.pbkdf2Sync('pass', 'salt', 100000, 64, 'sha512');
console.timeEnd('sync'); // ~100ms, всё это время сервер не отвечает
// Асинхронная версия уходит в thread pool
crypto.pbkdf2('pass', 'salt', 100000, 64, 'sha512', () => {
console.log('done'); // event loop свободен
});
Для CPU-нагруженных задач в Node.js есть модуль worker_threads, позволяющий запускать JS в отдельных потоках с изолированной памятью и обменом через postMessage. Модуль cluster позволяет запустить несколько процессов Node.js, разделяющих один порт, чтобы задействовать все ядра CPU.
Итог: Node.js однопоточен на уровне JavaScript, но многопоточен на уровне рантайма и ОС. Это даёт простую модель программирования без гонок и одновременно эффективное использование ресурсов для I/O-нагруженных приложений. Для тяжёлых вычислений стоит применять worker_threads или выносить расчёты в отдельные сервисы.