Мое решение проблемы:
"Дистрибьютор" - gen_server,
"рабочий" - gen_server.
«Дистрибьютор» запускает «работников», используя slave: start_link, каждый «работник» запускается с параметром max_processes,
"distributor" behavior:
handle_call(submit,...)
* put job to the queue,
* cast itself check_queue
handle_cast(check_queue,...)
* gen_call all workers for load (current_processes / max_processes),
* find the least busy,
* if chosen worker load is < 1 gen_call(submit,...) worker
with next job if any, remove job from the queue,
"worker" behavior (trap_exit = true):
handle_call(report_load, ...)
* return current_process / max_process,
handle_call(submit, ...)
* spawn_link job,
handle_call({'EXIT', Pid, Reason}, ...)
* gen_cast distributor with check_queue
На самом деле это сложнее, чем то, что мне нужно отслеживать запущенные задания, убивать их, если нужно, но это легко реализовать в такой архитектуре.
Хотя это не динамический набор узлов, но вы можете запустить новый узел от дистрибьютора, когда вам нужно.
P.S. Похоже на пул, но в моем случае я отправляю процессы порта, поэтому мне нужно ограничить их и лучше контролировать, что и куда происходит.