Релиз Node.js 10.5: мультипоточность из коробки

в 13:38, , рубрики: javascript, node.js, nodejs, параллельные вычисления

Релиз Node.js 10.5: мультипоточность из коробки - 1

На прошлой неделе состоялся релиз Node.js версии 10.5.0, содержащий нововведение, чью значимость трудно переоценить, – поддержку многопоточности в виде модуля worker_threads. Сразу оговорюсь API находится в экспериментальной стадии и поэтому может измениться, но уже сейчас можно составить первое впечатление и получить представление о заложенных в его основу принципах и технологиях. А если у вас есть желание, то и поучаствовать в финализации интерфейса, написании кода или исправлении багов (список issues).

История появления

На протяжении всей жизни Node.js единственным способом распараллелить вычисления был запуск нового процесса, например с использованим модуля cluster. По многим причинам такой подход не устраивает разработчиков, в частности потому, что это приводит к повтроной загрузке в память компьютера исполняемого кода Node.js со всеми встроенными модулями, что является неэффективным способом расходования ресурсов.

Тем неменее обсуждение внедрения многопоточности в Node.js всегда упиралось в сложность V8 и огромное количество неизвестных: как подключать нативные модули, разделять память, осуществлять коммуникацию между потоками и прочее. И пока разработчики искали с какой стороны подступиться к теме в вебе успешно был внедрен Worker API, который и стал ориентиром на начальных этапах. Разработка началась усилиями addaleax и была подхвачена сообществом.

В течение года велась активная работа, в процессе которой требования к дизайну были конкретизированы и API обрел собственные специфичные для Node.js черты, а сам модуль получил название worker_threads. Ниже я кратко опишу worker_threads в общих чертах, а для подробного изучения советую посетить страницу официальной документации.

Описание

Как уже было сказано выше, цель данной разработки – улучшение производительности при помощи распределенния нагрузки по раздельным потокам в рамках одного процесса, вместо запуска нескольких процессов. Поэтому потоки будут поддерживать подключение всех доступных основному процессу модулей (на данный момент нативные модули не поддерживаются).

Так же как и в Worker API взаимодействие между главным и дочерним потоком осуществляется посредством передачи передаваемых (Transferrable) объектов посредством postMessage, что позволяет избежать проблем одновременного доступа, хоть и требует дополнительных обращений к памяти для копирования данных. При этом объекты вроде SharedArrayBuffer сохраняют свое поведение и не вызывают переаллокации.

Из WebAPI был взят MessageChannel и MessagePort, что позволяет создавать изолированные каналы обмена сообщениями и передавать их между потоками.

Для того чтобы попробовать worker_threads в деле при запуске процесса необходимо указать специальный флаг:

node --experimental-worker main.js

Пример

Так как API еще может меняться я не буду его описывать, но приведу пример обмена сообщениями между родительским и дочерним потоком, в котором дочерний поток сообщает свой threadId, через MessagePort и завершает свою работу.

Главный поток

Пример кода основного потока:

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

const worker = new Worker(__dirname + '/worker.js');

worker.on('online', () => {
  console.log('Worker ready');
});

worker.on('message', (msg) => {
  console.log('Worker message:', msg);
});

worker.on('error', (err) => {
  console.error('Worker error:', err);
});

worker.on('exit', (code) => {
  console.log('Worker exit code:', code);
});

Дочерний поток

Дочерний поток живет пока его очередь событий (event loop) не опустеет. Таким образом сразу после выполнения кода из worker.js поток будет автоматически закрыт. Для связи с родителем используется parentPort:

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

parentPort.postMessage(`Hello from thread #${threadId}.`);
// Exit happens here

В дочернем потоке объект process переопределен, а его поведение несколько отличается от поведения process в родительском потоке. В частности нет возможности отреагировать на сигналы SIGNINT, изменить значения process.env, а вызов process.exit остановит только worker, но не весь процесс.

Заключение

Воркеры позволят сильно упростить создание приложений требующих взаимодействия между параллельно исполняемыми участками кода и, что особенно важно, делает коммуникацию и управление потоками наиболее очевидным способом. А так же позволят избежать платформозависимых ограничений вызванных различием Windows и Unix. Уверен, что открывающиеся возможности привлекут новых разработчиков, которые еще не сделали выбор в пользу Node.js. А пока продолжайте следить за изменениями и подключайтесь к процессу разработки API в репозитории.

Ссылки

Автор: rumkin

Источник