В образовательных целях я написал проект на Scala c использованием Play Framework’е. Это веб-приложение, с более-мене удобным интерфесом, которое позволяет запускать и управлять роботами для инстаграма. Подробнее тут…
Роботы должны работать параллельно для чего нужен удобный механизм. Такой механизм в Scala есть. Это акторы и Future.
Каждый робот запускается внутри актора и Future. И все работало прекрасно, до некоторого времени… Вскоре выяснилось, что приложение зависает после запуска очередного робота.
Методом различных ухищрений стало понятно, что приложение зависает на запуске восьмого робота.
Суть ошибки оказалось в том, что каждая Future выполняется в некотором контексте. Этот самый контекст определяет сколько одновременно Furute’s могут быть запущено. Стандартный глобальный контекст:
1 |
import scala.concurrent.ExecutionContext.Implicits.global |
Подразумевает, что может быть запущено столько Future’s, сколько ядер на процессоре. В Java есть класс:
1 |
import java.util.concurrent.Executors |
В этом классе определяются статические методы для создания соответсвующего экзекутора для контекста. Если порыться в коде, то можно обнаружить строчку, которая, вероятно, создает экзекутора по-умолчанию:
1 2 3 4 5 6 |
public static ExecutorService newWorkStealingPool() { return new ForkJoinPool (Runtime.getRuntime().availableProcessors(), ForkJoinPool.defaultForkJoinWorkerThreadFactory, null, true); } |
Видно, строчку, в которой есть «availableProcessors», что и определяет количество потоков.
Говоря проще в состоянии из-коробки можно запустить одновременно столько Future, сколько у вас ядер на процессоре. Остальные Future будут ждать, пока первые восемь не завершат свою работу. Именно поэтому приложение зависало после создания восьмого робота.
А решение простое, заменить контекст со стандартным экзекутором, на контекст с экзекутором с явным указанием количества потоков, например 100:
1 |
implicit val executionContext = ExecutionContext.fromExecutor( Executors.newWorkStealingPool(100)) |
Есть и более гибкие экзекуторы, которые создаются соответствующими статическими методами класса Executors.