Варианты использования для планировщиков RxJava

В RxJava есть 5 разных планировщиков на выбор:

  • немедленный(): создает и возвращает планировщик, который немедленно запускает работу в текущем потоке.

  • trampoline(): создает и возвращает планировщик, который работает в очереди на текущий поток, который будет выполнен после завершения текущей работы.

  • newThread(): Создает и возвращает Планировщик, который создает новый поток для каждой единицы работы.

  • computation(): Создает и возвращает планировщик, предназначенный для вычислительной работы. Это можно использовать для циклов событий, обработки обратных вызовов и другой вычислительной работы. Не выполняйте работу с IO-привязкой к этому планировщику. Используйте Schedulers. io().

  • io(): Создает и возвращает Планировщик, предназначенный для работы с привязкой к IO. Реализация поддерживается потоком потоков Executor, который будет расти по мере необходимости. Это можно использовать для асинхронного выполнения блокировки IO. Не выполняйте вычислительную работу над этим планировщиком. Используйте Schedulers. computation().

Вопросы:

Первые 3 планировщика довольно понятны; однако я немного запутался в вычислении и io.

  • Что такое "работа с IO-привязкой"? Используется ли он для обработки потоков (java.io) и файлов (java.nio.files)? Используется ли он для запросов к базе данных? Используется ли он для загрузки файлов или доступа к API REST?
  • Как вычисление() отличается от newThread()? Разве что все вызовы вычислений() отображаются в одном (фоновом) потоке, а не в новом (фоновом) потоке каждый раз?
  • Почему плохой вызов вычисление() при работе с IO?
  • Почему при выполнении вычислительной работы плохо называть io()?

Ответ 1

Хорошие вопросы, я думаю, что документация могла бы быть более подробной.

  1. io() поддерживается неограниченным пулом потоков и является той вещью, которую вы бы использовали для задач, не требующих большого объема вычислений, то есть вещей, которые не сильно нагружают ЦП. Таким образом, да, взаимодействие с файловой системой, взаимодействие с базами данных или службами на другом хосте являются хорошими примерами.
  2. computation() поддерживается ограниченным пулом потоков с размером, равным количеству доступных процессоров. Если вы попытались запланировать интенсивную работу ЦП параллельно между более чем доступными процессорами (скажем, с помощью newThread()), то вы готовы к накладным расходам на создание потоков и переключению контекста, поскольку потоки соперничают за процессор, и это потенциально может сильно снизить производительность.
  3. Лучше всего оставить computation() для работы с интенсивным использованием процессора, только в противном случае вы не получите хорошего использования процессора.
  4. Плохо вызывать io() для вычислительной работы по причине, описанной в 2. io() не ограничен, и если вы запланируете тысячу вычислительных задач на io() параллельно, то каждая из этих тысяч задач будет иметь свой собственный поток и будет конкурировать за процессор, несущий расходы на переключение контекста.

Ответ 2

Наиболее важным моментом является то, что как Schedulers.io, так и Schedulers.computation поддерживаются неограниченными пулами потоков, в отличие от других, упомянутых в вопросе. Эта характеристика разделяется только Schedulers.from(Executor) в случае, если Исполнитель создан с помощью newCachedThreadPool (без ограничений с пулом потоков автовосстановления).

Как объяснено в предыдущих ответах и нескольких статьях в Интернете, Schedulers.io и Schedulers.computation следует тщательно использовать, поскольку они оптимизированы для типа работы в их имени. Но, с моей точки зрения, они играют важную роль в обеспечении реального параллелизма реактивным потокам.

В противоположность мнению новичков, реактивные потоки не являются по своей сути параллельными, но по своей сути асинхронными и последовательными. По этой причине Schedulers.io будет использоваться только тогда, когда операция ввода-вывода блокируется (например: с помощью команды блокировки, такой как Apache IOUtils FileUtils.readFileAsString(...)), таким образом, заморозит вызывающий поток, пока операция не будет сделанный.

Использование асинхронного метода, такого как Java AsynchronousFileChannel (...), не будет блокировать вызывающий поток во время операции, поэтому нет смысла использовать отдельный поток. На самом деле, потоки Schedulers.io на самом деле не подходят для асинхронных операций, поскольку они не запускают цикл событий, и обратный вызов никогда не будет вызван.

Такая же логика применяется для доступа к базе данных или удаленных вызовов API. Не используйте Schedulers.io, если вы можете использовать асинхронный или реактивный API для совершения вызова.

Назад к параллелизму. У вас может не быть доступа к асинхронному или реактивному API для выполнения операций ввода-вывода асинхронно или одновременно, поэтому единственной альтернативой является отправка нескольких вызовов в отдельный поток. Увы, реактивные потоки последовательны по своим целям, но хорошей новостью является то, что оператор flatMap() может вводить параллелизм в своем ядре.

Параллелизм должен быть построен в потоковой конструкции, как правило, используя оператор flatMap(). Этот мощный оператор может быть сконфигурирован для обеспечения многопоточного контекста вашей встроенной функции flatMap() <T, R>. Этот контекст обеспечивается многопоточным планировщиком, таким как Scheduler.io или Scheduler.computation.

Более подробную информацию можно найти в статьях о RxJava2 Schedulers и Concurrency, где вы найдете пример кода и подробные объяснения того, как использовать планировщики последовательно и одновременно.

Надеюсь это поможет,

Softjake

Ответ 3

Этот пост в блоге дает отличный ответ

Из поста в блоге:

Schedulers.io() поддерживается неограниченным пулом потоков. Он используется для операций ввода-вывода без интенсивной загрузки ЦП, включая взаимодействие с файловой системой, выполнение сетевых вызовов, взаимодействие с базой данных и т.д. Этот пул потоков предназначен для асинхронного блокирующего ввода-вывода.

Schedulers.computation() поддерживается ограниченным пулом потоков размером до числа доступных процессоров. Он используется для вычислительной или ресурсоемкой работы, такой как изменение размера изображений, обработка больших наборов данных и т.д. Будьте осторожны: когда вы выделяете больше вычислительных потоков, чем доступных ядер, производительность снижается из-за переключения контекста и накладных расходов на создание потоков, поскольку потоки соперничают за время процессоров.

Schedulers.newThread() создает новый поток для каждой запланированной единицы работы. Этот планировщик дорог, так как каждый раз создается новый поток, и повторное использование не происходит.

Schedulers.from(Executor executor) создает и возвращает собственный планировщик, поддерживаемый указанным исполнителем. Чтобы ограничить количество одновременных потоков в пуле потоков, используйте Scheduler.from(Executors.newFixedThreadPool(n)). Это гарантирует, что если задача запланирована, когда все потоки заняты, она будет поставлена в очередь. Потоки в пуле будут существовать до тех пор, пока он не будет явно отключен.

Основной поток или AndroidSchedulers.mainThread() предоставляется библиотекой расширений RxAndroid для RxJava. Основной поток (также известный как поток пользовательского интерфейса) - это место, где происходит взаимодействие с пользователем. Следует позаботиться о том, чтобы не перегружать этот поток, чтобы не допустить дергания неотвечающего пользовательского интерфейса или, что еще хуже, диалога "Приложение не отвечает" (ANR).

Schedulers.single() является новым в RxJava 2. Этот планировщик поддерживается одним потоком, выполняющим задачи последовательно в запрошенном порядке.

Schedulers.trampoline() выполняет задачи в порядке FIFO (первый пришел, первый вышел) одним из участвующих рабочих потоков. Его часто используют при реализации рекурсии, чтобы избежать увеличения стека вызовов.