Akka docs утверждает, что диспетчером по умолчанию является fork-join-executor
, потому что он "дает отличную производительность в большинстве случаев".
Мне интересно, почему это так?
С ForkJoinPool
ForkJoinPool отличается от других видов ExecutorService в основном за счет использования кражи работы: все потоки в пуле пытаются найти и выполнитьзадачи, переданные в пул и / или созданные другими активными задачами (в конечном итоге блокирование ожидания работы, если таковой не существует)Это обеспечивает (1) эффективную обработку, когда большинство задач порождают другие подзадачи (как и большинство ForkJoinTasks) , а также (2), когда множество небольших задач передается в пул от внешних клиентов ,Особенно при установке asyncMode в true в конструкторах, ForkJoinPools может также (3) подходить для использования с задачами в стиле событий, которые никогда не объединяются.
Сначала я предполагаю, чтоAkka не является примером case (1), потому что я не могу понять, как Akka может выполнять задачи, я имею в виду, что будет задачей, которую можно выполнить во многих задачах?
Я вижу каждое сообщение какнезависимая задача, поэтому я думаю, что Akka похож на случай (2), где сообщения представляют собой множество небольших задач, отправляемых (через! и?) в ForkJoinPool
.
Следующим вопросом, хотя и не связанным строго с akka, будет , почему случай использования, в котором не используются fork и join (основные возможности ForkJoinPool
, позволяющие выполнять кражу рабочих данных), все еще можетбыть полезным для ForkJoinPool?
с Масштабируемость пула соединений Fork
Мы заметили, что число переключений контекста было ненормальным, выше 70000 в секунду.
Это должно быть проблемой, но чем это вызвано?Виктор выдвинул квалифицированное предположение, что это должна быть очередь задач исполнителя пула потоков, поскольку она является общей, и блокировки в LinkedBlockingQueue могут потенциально генерировать переключение контекста при возникновении конфликта .
Однако если верно, что Akka не использует ForkJoinTasks
, все задачи, отправленные внешними клиентами, будут поставлены в очередь в общей очереди, поэтому конкуренция должна быть такой же, как в ThreadPoolExecutor
.
Итак, мои вопросы:
- Akka использует
ForkJoinTasks
(case (1)) или относится к case (2)? - Почему
ForkJoinPool
выгодно в случае (2), если все эти задачи, отправленные внешними клиентами, будут помещены в общую очередь и не будет происходить кража работы? - Что может быть примером "с задачами в стиле событий, которыеникогда не присоединялся "(случай 3)?
Обновление
Правильный ответ от johanandren, однако я хочу добавить некоторые основные моменты.
- Akka не использует fork и joinвозможности, начиная с AFAIK с моделью Actor, или, по крайней мере, как мы ее реализуем, для этого (из комментария Йоханандрена) на самом деле нет прецедента.
Так что я понимаю, что Akka не является примером case (1) было правильно. - В своем первоначальном ответе я сказал, что все задачи, отправленные внешними клиентами, будут поставлены в очередь в общей очереди .
Это было правильно, но только для предыдущей версии (JDK7) из FJP.В jdk8 единичная очередь выдачи была заменена множеством "очередей отправки". Этот ответ объясняет это хорошо: Теперь, до (IIRC) JDK 7u12, ForkJoinPool имел единую глобальную очередь отправки.Когда у рабочих потоков закончились локальные задачи, а также задачи для кражи, они попали туда и попытались проверить, доступна ли внешняя работа.В этом проекте нет никаких преимуществ перед обычным, скажем, ThreadPoolExecutor, поддерживаемым ArrayBlockingQueue.[...]
Теперь внешняя отправка попадает в одну из очередей отправки.Затем работники, которым нечем заняться, могут сначала заглянуть в очередь отправки, связанную с конкретным работником, а затем побродить, просматривая очереди отправки других.Можно также назвать это «кражей работы».
Таким образом, это включало кражу работы в сценариях, где не использовалось форк-соединение.Как говорит Даг Ли,
Значительно лучшая пропускная способность, когда множество клиентов выполняет много задач.(Я измерял до 60-кратного микробенчмарка Speedupson).Идея состоит в том, чтобы обращаться с внешними отправителями так же, как с работниками, используя рандомизированные очереди и кражи.(Это потребовало большого внутреннего рефакторинга, чтобы разъединить рабочие очереди и рабочих.) Это также значительно улучшает пропускную способность, когда все задачи асинхронны и передаются в пул, а не разветвляются, что становится разумным способом структурировать структуры акторов, а также многие обычные сервисы, которые вы могли бы использовать в противном случае.ThreadPoolExecutor для.
- Есть еще одна особенность, о которой стоит упомянуть, о FJP, взятом из этого комментария
4% действительно не так много для FJP,С FJP вы по-прежнему должны идти на компромисс, о котором вам необходимо знать: FJP некоторое время продолжает вращать потоки, чтобы иметь возможность быстрее обрабатывать прибывающую работу.Это обеспечивает хорошую задержку во многих случаях.Тем не менее, особенно если ваш пул сверхобеспечен, компромисс - это небольшая задержка с увеличением энергопотребления в почти бездействующих ситуациях.