На прошлой неделе состоялся релиз 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 в репозитории.
Ссылки
- Документация по worker_threads.
- Автор фотографии Paul Smith.
Автор: rumkin