Використовуйте випадки для планувальників RxJava


253

У RxJava є 5 різних планувальників на вибір:

  1. neposredna () : Створює та повертає Scheduler, який негайно виконує роботу над поточним потоком.

  2. trampoline () : Створює та повертає Scheduler, який черги працює над поточним потоком, який буде виконаний після завершення поточної роботи.

  3. newThread () : Створює та повертає Планувальник, який створює нову Тему для кожної одиниці роботи.

  4. computation () : створює та повертає Scheduler, призначений для обчислювальної роботи. Це можна використовувати для циклів подій, обробки зворотних викликів та інших обчислювальних робіт. Не виконуйте роботи, пов'язані з IO, на цьому планувальнику. Використовуйте планувальники. натомість io () .

  5. io () : Створює та повертає Планувальник, призначений для роботи, пов'язаної з IO. Реалізація підтримується пулом потоків Executor, який зростатиме у міру необхідності. Це можна використовувати для асинхронного виконання блокування вводу-виводу. Не виконуйте обчислювальної роботи на цьому планувальнику. Використовуйте планувальники. обчислення () .

Запитання:

Перші 3 планувальники досить зрозумілі; однак я трохи заплутаний у обчисленні та io .

  1. Що саме "робота, пов'язана з IO"? Чи використовується для роботи з потоками ( java.io) та файлами ( java.nio.files)? Чи використовується для запитів до бази даних? Чи використовується для завантаження файлів або доступу до API REST?
  2. Чим обчислення () відрізняється від newThread () ? Це те, що всі виклики для обчислення () виконуються на одному (фоновому) потоці замість нового (фонового) потоку кожного разу?
  3. Чому погано викликати обчислення () під час роботи вводу-виводу?
  4. Чому погано викликати io () під час обчислювальної роботи?

Відповіді:


332

Чудові запитання, я думаю, що документація могла б зробити дещо детальніше.

  1. io()підтримується необмеженим пулом потоків і є тим видом, який ви використовуєте для не обчислювально інтенсивних завдань, тобто речей, які не навантажують багато процесора. Тож взаємодія з файловою системою, взаємодія з базами даних або службами на іншому хості - хороші приклади.
  2. computation()підтримується обмеженим пулом потоків розміром, рівним кількості доступних процесорів. Якщо ви спробували запланувати інтенсивну роботу процесора паралельно на більш ніж доступних процесорах (скажімо, використовуючи newThread()), тоді ви готові до створення потоку накладних даних та переключення контексту накладні, оскільки потоки бачать для процесора, і це, можливо, є великим хітом продуктивності.
  3. Краще залишитись computation()на інтенсивній роботі процесора, тільки в іншому випадку ви не отримаєте хорошого використання процесора.
  4. Дуже погано закликати io()до обчислювальної роботи з причини, про яку йдеться у статті 2. не io()є необмеженим, і якщо ви заплануєте тисячу обчислювальних завдань io()паралельно, то кожна з цих тисяч завдань буде мати свою власну нитку і змагатиметься за те, щоб процессор зазнав витрат на комутацію контексту.

5
Через ознайомлення з джерелом RxJava. Це було тривалий час для мене непорозумінням, і я думаю, що документація потребує покращення в цьому плані.
Дейв Мотен

2
@IgorGanapolsky Я здогадуюсь, що це те, що ви рідко хотіли б зробити. Створення нової нитки для кожної одиниці роботи рідко сприяє ефективності, оскільки нитки дорого побудувати і зірвати. Зазвичай ви хочете повторно використовувати потоки, які виконують обчислення () та інші планувальники. Єдиний раз, коли newThread () може мати законне використання (принаймні, я можу придумати) - це починати ізольовані, нечасті, тривалі завдання. Навіть тоді я можу використати io () для цього сценарію.
tmn

4
Чи можете ви показати приклад, де батут () був би корисним? Я розумію концепцію, але не можу розібратися в сценарії, який би я використовував на практиці. Це єдиний планувальник, який для мене досі залишається загадкою
tmn

32
Для мережевих дзвінків використовуйте Schedulers.io (), а якщо вам потрібно обмежити кількість одночасних мережевих дзвінків, використовуйте Scheduler.from (Executors.newFixedThreadPool (n)).
Дейв Мотен

4
Ви можете подумати, що введення timeoutза замовчуванням computation()буде блокувати нитку, але це не так. Під обкладинками computation()використовується ScheduledExecutorServiceнастільки відкладені в часі дії не блокувати. Враховуючи цей факт computation(), це гарна ідея, тому що якби він був на іншій нитці, ми б зазнали витрат на перемикання потоку.
Дейв Мотен

3

Найважливішим моментом є те, що і Schedulers.io, і Schedulers.computation підтримуються необмеженими пулами потоків на відміну від інших, зазначених у питанні. Ця характеристика поділяється лише Schedulers.from (Виконавець) у випадку, якщо Виконавець створений з newCchedThreadPool (не обмежений пулом автоматичного відновлення потоку).

Як рясно пояснено у попередніх відповідях та кількох статтях в Інтернеті, 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 and Concurrency, де ви знайдете зразок коду та докладні пояснення щодо того, як користуватися Schedulers послідовно та одночасно.

Сподіваюся, це допомагає,

Softjake


2

Ця публікація в блозі дає чудову відповідь

З публікації щоденника:

Schedulers.io () підтримується пулом без обмежених потоків. Він використовується для роботи без введення процесора вводу / виводу, включаючи взаємодію з файловою системою, виконання мережевих дзвінків, взаємодії з базами даних тощо. Цей пул потоків призначений для використання в асинхронному виконанні блокування вводу-виводу.

Schedulers.computation () підтримується обмеженим пулом потоків розміром до кількості доступних процесорів. Він використовується для обчислювальної або процесорно-інтенсивної роботи, таких як зміна зображень, обробка великих наборів даних тощо. Будьте уважні: коли ви виділите більше потоків обчислень, ніж наявних ядер, продуктивність знизиться через переключення контексту та створення потоку накладних даних, як потоки час процесорів.

Schedulers.newThread () створює нову нитку для кожної запланованої одиниці роботи. Цей планувальник коштує дорого, оскільки новий потік породжується кожного разу, і повторне використання не відбувається.

Schedulers.from (виконавець Executor) створює та повертає користувальницький планувальник, підкріплений вказаним виконавцем. Щоб обмежити кількість одночасних потоків у пулі потоків, використовуйте Scheduler.from (Executors.newFixedThreadPool (n)). Це гарантує, що якщо завдання планується, коли всі потоки зайняті, воно буде в черзі. Нитки в пулі існуватимуть до тих пір, поки воно не буде явно закрито.

Основна нитка або AndroidSchedulers.mainThread () надається бібліотекою розширень RxAndroid RxJava. Головний потік (також відомий як потік інтерфейсу) - це місце взаємодії з користувачем. Слід бути обережним, щоб не перевантажувати цю тему, щоб запобігти невмілий інтерфейс, що не реагує, або, що ще гірше, діалогове вікно програми не відповідає ”(ANR).

Schedulers.single () є новим у RxJava 2. Цей планувальник підтримується одним потоком, що виконує завдання послідовно, у порядку, що запитується.

Schedulers.trampoline () виконує завдання FIFO (First In, First Out) одним із робочих потоків, що беруть участь. Він часто використовується при здійсненні рекурсії, щоб уникнути збільшення стеку викликів.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.