Pull to refresh

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

Reading time 3 min
Views 32K


На прошлой неделе состоялся релиз 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 в репозитории.


Ссылки


Tags:
Hubs:
+37
Comments 45
Comments Comments 45

Articles