Процессы и потоки в Node.js

Понимание архитектуры выполнения в JavaScript

Материал для технической аудитории (middle+/senior)

Введение

Понимание концепции процессов и потоков - ключ к эффективной разработке в Node.js

  • Что такое процесс?
  • Что такое поток?
  • Почему важно различать эти понятия?

Процессы vs Потоки

Процесс

  • Независимая единица выполнения
  • Имеет собственную память
  • Изоляция ресурсов
  • Высокая стоимость создания
  • Межпроцессное взаимодействие

Поток

  • Внутрипроцессовая единица выполнения
  • Делится памятью с другими потоками
  • Низкая стоимость создания
  • Общий доступ к ресурсам
  • Межпотоковое взаимодействие

JavaScript - однопоточный

Основные принципы однопоточности:

  • Один поток выполнения
  • Событийно-ориентированная модель
  • Асинхронное выполнение
  • Одновременно только один фрагмент кода
// Пример однопоточного выполнения
console.log("1");
setTimeout(() => console.log("2"), 0);
console.log("3");
// Вывод: 1, 3, 2

Почему Node.js однопоточный

  • Облегченная модель выполнения
  • Избежание гонок данных
  • Простота отладки
  • Эффективное управление ресурсами
  • Одна точка входа в событийный цикл

В отличие от многопоточных языков, Node.js использует event loop для обработки асинхронных операций

Структура Node.js

Основные компоненты

  • Event Loop
  • Libuv (асинхронная библиотека)
  • JavaScript Engine (V8)
  • Системные вызовы

Архитектура

┌─────────────────┐
│   Application   │
├─────────────────┤
│   JavaScript    │
├─────────────────┤
│   V8 Engine     │
├─────────────────┤
│   Node.js API   │
├─────────────────┤
│   Libuv         │
├─────────────────┤
│   OS/Kernel     │
└─────────────────┘

Многопоточность в Node.js

Node.js использует многопоточность на уровне системы:

  • Потоки ввода-вывода (IO threads)
  • Потоки для парсинга CSS
  • Потоки для работы с файлами
  • Потоки для сетевых операций
  • Потоки для выполнения CPU-intensive задач

Libuv и потоки

Libuv - библиотека для асинхронных операций:

  • Конфигурируемое количество потоков
  • По умолчанию 4 потока для асинхронных операций
  • Можно изменить через UV_THREADPOOL_SIZE
export UV_THREADPOOL_SIZE=8
node app.js

Потоки Libuv используются для задач, которые могут блокировать основной поток

Примеры использования потоков

Системные вызовы

  • Файловая система
  • Сетевые операции
  • Шифрование
  • Работа с базами данных

Пользовательские потоки

const { Worker, isMainThread, parentPort } = require('worker_threads');

if (isMainThread) {
  const worker = new Worker(__filename);
  worker.on('message', (msg) => {
    console.log('Received:', msg);
  });
} else {
  parentPort.postMessage('Hello from worker');
}

Браузер vs Node.js

Браузер

  • Один поток для UI
  • Web Workers для параллелизма
  • Ограниченный доступ к ресурсам
  • Ограничения безопасности

Node.js

  • Один поток для JavaScript
  • Worker Threads для CPU-интенсивных задач
  • Полный доступ к системным ресурсам
  • Многопоточность через Libuv

Практические примеры

Использование Worker Threads

// worker.js
const { parentPort } = require('worker_threads');

parentPort.on('message', (data) => {
  // CPU-intensive задача
  const result = heavyCalculation(data);
  parentPort.postMessage(result);
});

function heavyCalculation(input) {
  let sum = 0;
  for (let i = 0; i < input; i++) {
    sum += i * i;
  }
  return sum;
}

Пример Worker Threads

// main.js
const { Worker } = require('worker_threads');
const worker = new Worker('./worker.js');

worker.postMessage(1000000);
worker.on('message', (result) => {
  console.log('Результат:', result);
});

worker.on('error', (error) => {
  console.error('Ошибка:', error);
});

worker.on('exit', (code) => {
  if (code !== 0) {
    console.error('Worker завершился с кодом', code);
  }
});

Рекомендации по использованию

  • Используйте Event Loop для I/O операций
  • Используйте Worker Threads для CPU-интенсивных задач
  • Настройте UV_THREADPOOL_SIZE для ваших задач
  • Не перегружайте систему потоками
  • Учитывайте overhead при создании потоков

Сценарии использования

Используйте Event Loop когда:

  • Файловые операции
  • Сетевые вызовы
  • Базы данных
  • Таймеры и задержки
  • События пользовательского интерфейса

Используйте Worker Threads когда:

  • Математические вычисления
  • Обработка больших объемов данных
  • Шифрование/дешифрование
  • Работа с изображениями
  • Анализ логов

Libuv - ядро асинхронности Node.js

Libuv - это кроссплатформенная библиотека для асинхронного ввода-вывода:

  • Написана на C
  • Обеспечивает event loop
  • Управляет потоками (thread pool)
  • Работает с системными вызовами
  • Поддерживает разные операционные системы
┌─────────────────┐
│   Node.js       │
│   V8 Engine     │
│   Libuv         │
├─────────────────┤
│   OS/Kernel     │
└─────────────────┘

Libuv обеспечивает асинхронность на уровне операционной системы

Архитектура Libuv

Основные компоненты

  • Event Loop
  • Thread Pool
  • I/O Overlays
  • Async Handle
  • Timer Handle

Thread Pool

  • По умолчанию 4 потока
  • Настройка через UV_THREADPOOL_SIZE
  • Используется для блокирующих операций
  • Управление очередью задач

Производительность и overhead

  • Event Loop: низкий overhead, высокая эффективность для I/O
  • Worker Threads: overhead при создании потока
  • Оптимальное количество потоков: обычно 2-4 на CPU
  • Используйте кэширование и пул потоков
// Пример сравнения производительности
const { Worker } = require('worker_threads');

// Для CPU-интенсивных задач
const worker = new Worker('./cpu-intensive.js');
worker.postMessage(data);

// Для I/O задач
const fs = require('fs');
fs.readFile('file.txt', 'utf8', (err, data) => {
  // Обработка данных
});

Проблемы и лучшие практики

Частые проблемы:

  • Блокировка основного потока
  • Неправильное управление памятью
  • Проблемы с передачей данных между потоками
  • Слишком много потоков

Решения:

  • Используйте Worker Pool
  • Ограничивайте количество одновременных потоков
  • Используйте память с умом
  • Мониторьте использование ресурсов

Мониторинг и отладка

  • Используйте Node.js Inspector для профилирования
  • Мониторьте использование CPU и памяти
  • Отслеживайте блокировки потоков
  • Используйте метрики производительности
# Запуск с профайлером
node --inspect-brk app.js

# Проверка производительности
node --max-old-space-size=4096 app.js

Сравнение с другими runtime

Язык/Платформа Модель Потоки Особенности
Node.js Однопоточный с Event Loop Libuv (системные) Worker Threads для CPU
Python Многопоточный с GIL CPython: ограничения Использует multiprocessing
Java Многопоточный Встроенные потоки Полноценная модель потоков

Thread Pool в Node.js

  • Набор заранее созданных системных потоков
  • Внутри одного процесса Node.js
  • Используется для блокирующих операций
  • По умолчанию 4 потока (UV_THREADPOOL_SIZE)
  • Скрыт внутри libuv
// Пример использования блокирующих операций
const crypto = require('crypto');

// Эти операции используют thread pool
crypto.pbkdf2('password', 'salt', 100000, 64, 'sha512', (err, key) => {
  console.log(key.toString('hex'));
});

Блокирующие операции

Операции, которые не могут быть выполнены асинхронно:

  • Хэширование (bcrypt, PBKDF2, crypto)
  • Сжатие данных (zlib)
  • Часть файловых операций (fs)
  • Некоторые DNS-запросы
  • Работа с большими объемами данных

Эти операции передаются в thread pool для выполнения

Обработка через Thread Pool

  1. JavaScript инициирует блокирующую операцию
  2. Libuv передает задачу в thread pool
  3. Один из потоков выполняет операцию
  4. Результат возвращается в event loop
  5. Callback/Promise/async-await обрабатывает результат
JavaScript → Libuv → Thread Pool → OS
     ↓         ↓          ↓        ↓
Event Loop ← Result ← Thread ← System Calls

Thread Pool vs Worker Threads

Thread Pool

  • Скрытый внутри libuv
  • Используется инфраструктурой
  • Для блокирующих операций
  • По умолчанию 4 потока
  • Не требует явного создания

Worker Threads

  • Явно создаются программистом
  • Для CPU-интенсивных задач
  • Собственный JS runtime/V8
  • Изоляция кода и памяти
  • Полный контроль над потоками

Заключение

  • Node.js использует один основной JavaScript-поток
  • Libuv реализует event loop и thread pool
  • Thread pool для блокирующих операций
  • Worker Threads для CPU-интенсивных задач
  • Правильное использование ресурсов - ключ к эффективности

Понимание этих концепций помогает создавать более эффективные приложения

Вопросы?

Спасибо за внимание!

Техническая аудитория (middle+/senior)