Комментарии 45
Всё бы хорошо — но пока нет возможности передавать объекты. А без этого фича не очень полезная. Но есть надежда, что допилят.
А где про это написано?
The super-high-level description of the implementation here is that Workers can share and transfer memory, but not JS objects (they have to be cloned for transferring), and not yet handles like network sockets.
Уже который год жду, пока в ноду завезут сериализацию для полноценных объектов. Но увы — кроме редко работающего node-serialize от luin пока ничего нет, и это меня который год удивляет.
Если не копировать объекты, то получите состояние гонки. Данное решение необходимо чтобы избежать проблем одновременного доступа. Тот же Erlang использует копирование всегда, при этом считается очень производительным. При этом дочерний поток – это новый контекст, который ничего не знает о том как выглядит прототип объекта из другого контекста.
Этот же подход используется в WebWorker API и скорее всего будет применяться и в Node.js.
В Эрланге копирование всегда, но с важной оговоркой: бинарные данные размером больше 64 байт не копируются, а хранятся в общей куче и управляются по счётчику ссылок. А в виде бинарных данных в Эрланге принято обрабатывать почти все "данные". Для примера посмотрел на своем приложении:
- binary: 709Мб
- process: 124Мб
- ets (ещё один способ шарить данные между процессами): 118Мб
Т.е. из 1Гб памяти потенциально может быть скопировано при передаче между процессами только 124Мб.
Ну и для копирования данных в Erlang под капотом используется memcpy а не сериализация в строку / json. Плюс собственная сложная система аллокаторов памяти заточенная на такое обращение.
Сокеты между erlang процессами можно передавать, но нужно для этого использовать отдельное API либо "обернуть" сокет в процесс. Тогда вообще никаких ограничений.
Спасибо за детальный ответ. Про IPC в статье указано, что будет использоваться механизм Worker API для передачи так называемых transferrable объектов, этот механизм оптимизирован в V8 (насколько это возможно) и хотя вряд ли сравнится с Erlang по производительности, как минимум из-за большей вариативности, это все же не обычная сериализация, так как передаваться будут и инстансы некоторых объектов.
Так эрланг — это функциональщина, значит неизменяемость и возможность безболезненно шарить данные. Зато изменение через создание нового объекта, так что в одном месте копирования избежали, зато в другом получили
Есть блобы и blob-URL. Возможно ли их как то применить для этих целей?
Передавайте легкие обьекты в которых только данные нужные для вычисления. Зачем тянуть весь объект?
Я понимаю ваше разочарование но думаю возможнось передавать объекты добавила бы больше проблем и сложностей чем полезностей
нестоит недооценивать
это круто не только в плане всякого кадра памяти, но ещё и это автоматом неизменяемая структура — что передали то и осталось
Хотя возможно стоило бы разрешить передавать фрозен объекты только на чтение.
как же шаред и прочее?
память получается необщая?
А в чем принципиальное отличие от child process api?
Различие API или плюсы перед child process?
Некоторые плюсы я описал в статье (меньшее количество аллокаций памяти и более удобная и быстрая коммуникация между потоками), подробнее опишу, когда API финализируется и список получится более точным. Но для меня плюс в большем контроле: child_process может быть завершен другим процессом, а worker – нет.
В мире java: многопоточность сложна, возьмем netty и будем писать в один поток!
В мире node: подержи мое саке, бака гайдзин!..
Эта фича — она как child_process, только на потоках. Принципиально ничего нового.
С чем сравниваем ноду: с CGI/FastCGI, и пулом потоков(как в ASP.NET)
Юзкейс 1 — приложение в котором практически нет сложной вычислительной логики, а только операции ввода/вывода:
1) В CGI на каждый запрос создается и убивается отдельный процесс
Проигрываем на времени создания процесса, на памяти под отдельные процессы, и на переключении контекста между процессами
При этом большую часть времени каждый процесс находится в ожидании одной операции чтения/записи
2) В FastCGI процессы переиспользуются, и достаются/возвращаются из пула по мере необходимости.
Пропали потери на создание/уничтожение процесса, но память и переключение контекста остались
3) Пул потоков. Примерно тоже, что и в предыдущем пункте, только меньше накладных расходов на память и переключение контекста
4) Однопоточная асинхронная архитектура(Node.js, Twisted, ...)
Процесс и поток один. Нету накладных расходов на память и переключение контекста(если не считать соседние и системные процессы).
При условии что нету блокирующих вычислительных операций, все очень хорошо в сравнении с предыдущими пунктами
Юзкейс 2 — в приложении есть блокирующая вычислительная логика
Допустим время одного запроса 1 секунда, из них 0.1 — вычисления, и 0.9 — ожидание ввода вывода. И на сервер приходит в среднем 100 запросов в секунду
1-3) в среднем будет 100 одновременно активных процессов/потоков(с сопутствующими накладными расходами)
4.1) child_process/cluster. В среднем должно хватить 10 воркер-процессов, каждый из которых будет на 100% занят вычислениями(в перерывах между ними получая результаты ввода/вывода от других запросов)
Хотя возможна проблема когда приходят два одновременных запроса в один воркер, и тогда второму придется дождаться завершения блокирующей операции от первого запроса(+100мс для конкретного запроса).
Но можно увеличить количество воркеров(не столь значительно как для 1-3 подходов), и уменьшить потенциальный разброс
4.2) То же самое, только уменьшаются накладные расходы на процессы, и обмен сообщениями между ними
Таким образом Node.js и в данном юзкейсе хорошо справляется(используя меньшее количество процессов/потоков)
Если да, звучит здорово.
Если же под отдельную операцию ввода/вывода нужно взять отдельный поток из пула, и работать с этой задачей асинхронно, то в плане нагрузки вроде ничего не меняется.
Если возможность загнать все продолжения в один поток — но не в ASP.NET.
Кстати, для справки: механизм async/await в C# появился раньше чем в javascript (собственно, из C# его и стянули).
А как бы вы эту задачу решали при использовании полноценных потоков в других языках :-)?
Не могу придумать, где это может пригодиться. Исключение – это исключение, оно сообщает об ошибке и более ни для чего использоваться не должно. Сигнализировать воркеру, что в нем что-то пошло не так извне грозит сложным дебагом. В любом случае такое поведение не является частью стандарта, поэтому не стоит расчитывать на его появление, но лучше написать об этом в обсуждении PR на гитхабе.
Не могу придумать, где это может пригодиться.
Например пришел запрос на вычисление cpu-bound задачи — мы отправили эту задачу в отдельный воркер (не создавая каждый раз новый и не убивая после завершения вычисления). Но тут вдруг запрос прерывается или сообщает что задача уже неактуальна а значить нет смысла ждать пока этот воркер закончит работу над этой задачей и надо как-то сообщить ему чтобы он прекратил вычисления. А исключение это просто как способ прервать какие-то синхронно вычисляемые задачи как например стек вызываемых функций или просто цикл.
Для таких вещей есть генераторы. Собственно делают то, что вам нужно.
Релиз Node.js 10.5: мультипоточность из коробки