TGStat
TGStat
Введите текст для поиска
Расширенный поиск каналов
  • Язык сайта
    flag Russian flag English flag Uzbek
  • Вход на сайт
  • Каталог
    Каталог каналов и чатов Региональные подборки Тематические подборки Платные каналы Поиск каналов
    Добавить канал/чат
  • Рейтинги
    Рейтинг каналов Рейтинг чатов Рейтинг публикаций
    Рейтинги брендов и персон
  • Аналитика
  • Поиск по публикациям
  • Мониторинг Telegram
  • Продвижение
    Реклама через Яндекс Бизнес Реклама в каналах через TGStat Agency Реклама на сайте TGStat.ru
Mobile VK Hub

8 Sep, 18:14

Открыть в Telegram Поделиться Пожаловаться

Dispatchers.IO: почему limitedParallelism не ограничивает то, что вы думаете

Приложение ходит в базу и во внешний API, оба вызова обёрнуты в withContext(Dispatchers.IO). Работает, пока API отвечает быстро. Потом на той стороне что-то ломается, ответы идут по 10 секунд — и вместе с ними встаёт локальная база, которая свободна и вообще ни при чём.

Причина в том, что Dispatchers.IO один на всё приложение. Медленный источник держит потоки ожиданием, остальные задачи стоят за ним в очереди. Разделить их может limitedParallelism, только работает эта функция не так, как обещает название.

Что такое Dispatchers.IO на самом деле

Под капотом — не отдельный пул, а представление общего планировщика, который делят IO и Default. Потоки создаются по мере надобности и гасятся, когда простаивают.

Ограничение у IO — 64 параллельные задачи или число ядер, если их больше; меняется свойством kotlinx.coroutines.io.parallelism. Число — это про задачи, а не про потоки: гарантии, что потоков будет ровно столько, никто не даёт.

У Default картина другая — параллелизм равен числу ядер, минимум два. Логика понятная: он для вычислений, а параллельно их не выполнить больше, чем есть ядер.

limitedParallelism на IO ничего не ограничивает

Название подсказывает, что вызов отрезает долю внутри тех же 64 задач. На IO всё наоборот.
val dbDispatcher  = Dispatchers.IO.limitedParallelism(100)
val apiDispatcher = Dispatchers.IO.limitedParallelism(60)
В документации формулировка прямая: представления, полученные через limitedParallelism, ограничением Dispatchers.IO не связаны. На пике система может держать 64 задачи самого IO плюс 100 плюс 60 параллельно. Каждый вызов заводит себе отдельную квоту сверху, а не отрезает кусок общей.

Логика за этим такая: IO про блокирующие операции, где поток большую часть времени просто ждёт. Общий жёсткий лимит на них бессмысленен, ожидающий поток почти не стоит процессорного времени.

Расплата за удобство — легко наплодить потоков. Пять модулей, каждый со своим limitedParallelism(50), дают на пике 250 параллельных блокирующих задач. В спокойном режиме потоков будет мало: представления делят общий пул, лишние гасятся. А вот пиковую картину придётся считать самому.

На Default всё наоборот

Тот же вызов на Dispatchers.Default ведёт себя противоположно: он именно режет, создавая представление на тех же потоках, которое занимает не больше указанного числа.
val imageDispatcher = Dispatchers.Default.limitedParallelism(4)
Тяжёлая обработка не съест все ядра и не заморозит остальные вычисления. Здесь название функции описывает происходящее честно.

Разница в коде никак не видна: две внешне одинаковые строки делают противоположные вещи, и понять это можно только по тому, от какого диспетчера идёт вызов.

Как этим пользоваться

Заводите отдельный диспетчер на каждый внешний источник. База, сеть, файлы — у каждого своя квота, и медленный источник перестаёт тормозить соседей.
class ApiClient(private val http: HttpClient) {
    private val dispatcher = Dispatchers.IO.limitedParallelism(20)

    suspend fun fetch(url: String) = withContext(dispatcher) {
        http.get(url)
    }
}
Размер квоты стоит соотносить с тем, что на другом конце. Пул соединений к базе на 10 штук делает бессмысленным диспетчер на 100: 90 задач просто встанут в ожидание свободного соединения, занимая потоки впустую. Держите квоту близкой к размеру пула.

И держите в голове сумму. Каждая новая квота на IO добавляется к общему пиковому числу, а не берётся из уже выделенного.

А вы разделяете диспетчеры по источникам или ходите везде через общий Dispatchers.IO? Расскажите в комментариях, ловили ли ситуацию, когда одна медленная зависимость подвешивала всё остальное.

#mobilevkhub #kotlin

354 0 7 15
Каталог
Каталог каналов и чатов Подборки каналов Поиск каналов Добавить канал/чат
Рейтинги
Рейтинг каналов Telegram Рейтинг чатов Telegram Рейтинг публикаций Рейтинги брендов и персон
API
API статистики API поиска публикаций API Callback
Наши каналы
@TGStat @TGStat_Chat @telepulse @TGStatAPI
Почитать
Академия TGStat Исследование Telegram 2019 Исследование Telegram 2021 Исследование Telegram 2023
Контакты
Справочный центр Поддержка Почта Вакансии
Всякая всячина
Пользовательское соглашение Политика конфиденциальности Публичная оферта
Наши боты
@TGStat_Bot @SearcheeBot @TGAlertsBot @tg_analytics_bot @TGStatChatBot