Вступление
00:00:00Сопрограммы Kotlin как потрясающая функция Kotlin особенно привлекателен благодаря coroutines - облегченной модели параллелизма, недоступной в самой Java. Разработчики демонстрируют эффективный запуск десятков тысяч сопрограмм, демонстрируя масштабируемость. Эта возможность выгодна как серверным сервисам, так и приложениям для Android, которым требуется недорогой параллелизм. Обсуждение сосредоточено на использовании серверной части, при этом отмечается широкая применимость.
Пример использования асинхронной обработки заказов Обработчик заказа должен получить пользовательский токен, зарезервировать продукт и сохранить данные о клиенте. Если услуга использования токена недоступна, поток сообщений прекращается с соответствующим ответом; если продукта нет на складе, пользователь уведомляется об этом. Блокирующие клиенты, такие как RestTemplate, просты, но связывают потоки, в то время как неблокирующие клиенты (например, WebClient) возвращают фьючерсы. Цель состоит в том, чтобы сохранить читаемость кода при использовании неблокирующего ввода‑вывода.
Фьючерсы и пулы потоков вводят обратные вызовы Асинхронное выполнение блокировки обычно приводит к перегрузке пула потоков и возврату Future. Объединение в цепочки с обратными вызовами (thenAccept, handle и т.д.) освобождает потоки, но вводит вложенные лямбда-выражения. Это работает функционально, но увеличивает нагрузку на объекты и когнитивные функции. Четкость кода страдает по мере накопления обратных вызовов.
Реактивные цепочки увеличивают синтаксические издержки Реактивные типы, такие как Mono, требуют flatMap и subscribe для создания вызовов compose. Каждый этап становится обратным вызовом, получающим предыдущие результаты и создающим новых публикаторов. Результирующие цепочки являются мощными, но подробными, с глубокой вложенностью. Этот стиль сочетает простоту с неблокирующим поведением.
Обратный вызов, черт возьми, вредит удобочитаемости и отладке Вложенные обратные вызовы создают чрезмерные отступы и помехи в виде скобок, известные как "ад обратного вызова". Такой код трудно читать, анализировать и отлаживать. Разработчики JavaScript столкнулись с этим в Promises, когда цепочки стали более сложными. Желателен более четкий, прямой стиль.
Пишите прямой код, выполняйте асинхронно Разработчикам нужен императивный, последовательный код, который, тем не менее, выполняется асинхронно. Kotlin обеспечивает это с помощью функций suspend, позволяющих приостанавливать вычисления, выраженные в простом синтаксисе. Язык содержит минимум ключевых слов; библиотеки предоставляют богатый набор инструментов для асинхронного программирования. В результате получаются прозрачные, читаемые потоки, которые не блокируются под капотом.
Сопрограммы - это легкие Потоки Сопрограмма ‑ это легкий пользовательский поток, который, по сути, является частью программы, которая может быть приостановлена и возобновлена. Многие сопрограммы объединяются в несколько потоков операционной системы, координируемых диспетчерами. Эта модель M:N обеспечивает масштабируемость и оперативность реагирования. Java Loom преследует аналогичные цели, но Kotlin предлагает это сегодня.
Совместная многозадачность при многомерном планировании Сопрограммы сохраняют свое состояние в оперативной памяти и планируются совместно. Диспетчер распределяет выполнение между сопрограммами вместо того, чтобы полагаться на предварительное планирование операционной системы. Это снижает затраты на переключение контекста и позволяет выполнять огромное количество задач равномерно. Эта модель соответствует современным нагрузкам на серверную часть, которые перегружают проекты, ориентированные на выполнение потоков для каждой задачи.
В основе Модели лежат продолжения Понимание сопрограмм требует понимания продолжений. Большая часть реализации сопрограмм в Kotlin основана на стиле передачи продолжения (CPS). Продолжения представляют собой остальную часть вычислений плюс их текущее состояние. Эта концепция объясняет, как на самом деле работают приостановка и возобновление.
От возвратов к CPS с явными продолжениями Функцию можно переписать, чтобы она выполняла обратный вызов вместо возврата значения. Обратный вызов получает вычисленный результат, и поток продолжается рекурсивно с помощью обратных вызовов. В этом суть CPS, который давно используется в таких языках, как Scheme. Он устраняет возвращаемые значения, передавая продолжение явно.
Продолжение Равно Оставшейся Работе Плюс Состояние Продолжение содержит в себе как следующие шаги для выполнения, так и данные, необходимые для их выполнения. Это явный параметр, с помощью которого передаются будущие события. В этом стиле состояние и поток управления передаются вместе. Эта двойственная природа является ключом к приостановке и возобновлению вычислений.
Интерфейс продолжения работы Kotlin
Kotlin моделирует продолжения с помощью Continuation
CoroutineContext Содержит метаданные выполнения CoroutineContext - это разнородная карта элементов, представляющих текущее состояние выполнения. Она содержит диспетчеров, задания и произвольные элементы контекста и позволяет перехватывать продолжения. Контекст может быть составлен и передан извне, чтобы влиять на поведение. Он также передает результаты по всем точкам приостановки во время возобновления работы.
Что делает suspend во время компиляции Компилятор преобразует функцию приостановки, добавляя параметр завершающего продолжения. В виртуальной машине JVM возвращаемый тип сигнатуры становится Any, что позволяет использовать либо реальное значение, либо специальный маркер приостановки. Для вызова таких функций из Java требуется явное указание продолжения. Это преобразование позволяет среде выполнения управлять приостановкой и возобновлением работы.
Приостановленные функции становятся конечными автоматами Каждая точка приостановки становится помеченным состоянием, управляемым компилятором. Локальные значения сохраняются, и поток управления компилируется в структуру, подобную switch или goto, над полем label. Сгенерированный автомат переключается между метками при каждой приостановке и возобновлении. Таким образом, последовательный исходный код преобразуется в явный конечный автомат.
Пошаговый просмотр ярлыков и резюме Выполнение начинается с метки 0 и вызывает первый вызов приостановки. Перед приостановкой код устанавливает следующую метку и возвращает маркер приостановки. При возобновлении управление передается сохраненной метке, продолжается и устанавливает последующие метки перед дальнейшими приостановками. Повторение с помощью этого механизма продолжается до тех пор, пока не будет получено окончательное значение.
Байт-Код Показывает состояние переключения Декомпилированный код JVM показывает переключение в поле метки продолжения. Метод повторно запускается рекурсивно, переходя к соответствующему случаю для продолжения работы. Маркеры приостановки различают подлинные результаты и приостановленное выполнение. Компилятор скрывает эту сложность за линейным, удобочитаемым Kotlin.
Внутренние крючки и перехватчики требуют осторожности Низкоуровневые перехватчики, такие как suspendCoroutine и перехват продолжения, позволяют библиотекам интегрироваться с механизмом приостановки. Они позволяют сохранять и возобновлять продолжения или переносить их в инструментарий. Неправильное использование может нарушить работу ядра сопрограммы, поэтому эти API нацелены на авторов библиотек. Разработчикам приложений следует избегать их, если они не знают о рисках.
Разработчики, задания и контекстное управление выполнением Сопрограммы всегда выполняются в контексте и обычно запускаются с помощью компоновщиков. запуск создает сопрограмму и возвращает дескриптор задания, привязывая его к контексту и диспетчеру. Компоновщики определяют, где и как выполняется работа. Компоновка контекста управляет поведением без изменения бизнес-кода.
Диспетчеры выбирают потоки Диспетчеры определяют многопоточность: по умолчанию для задач, привязанных к процессору, IO для блокировки вызовов, Main для работы с пользовательским интерфейсом и Uncontinented для гибкого возобновления. Они определяют, где запускается сопрограмма и где она может возобновиться. Переключение диспетчеров изменяет многопоточность с помощью withContext или контекстной композиции. Это обеспечивает точный контроль над выполнением.
Интеграция локальных данных потоков и данных платформы Элементы контекста могут передавать локальные данные потока, такие как регистрация MDC, через приостановки. Библиотеки интеграции объединяют контекст сопрограммы с поточно‑ориентированными платформами. Это обеспечивает согласованную диагностику и поведение платформы при асинхронном выполнении. Данные передаются вместе с сопрограммой, а не с потоком.
CoroutineScope обеспечивает структурированный параллелизм Область действия определяет границу жизненного цикла для сопрограмм. Код не может выйти из области действия, пока не завершатся все ее сопрограммы, что предотвращает утечки. Сборщики, такие как launch, являются дополнительными функциями в CoroutineScope, привязывающими задачи к этому жизненному циклу. Эта структура заменяет специальные шаблоны "уволить и забыть".
Передача пользовательских данных с помощью элементов контекста Пользовательские ключи и значения могут быть добавлены в контекст для передачи данных, относящихся к конкретному запросу. Эта информация остается доступной во всех точках приостановки. Диспетчеры и другие элементы работают с этими значениями в едином контексте. Бизнес-логика может извлекать их и обрабатывать по мере необходимости.
Отмена и тайм‑ауты как первоклассные операции Задание, возвращенное при запуске, может быть отменено, чтобы остановить текущую работу. join ожидает завершения после отмены или обычного завершения. withTimeout устанавливает ограничение по времени и вызывает исключение CancellationException по истечении срока действия. Эти примитивы предсказуемо ограничивают работу и освобождают ресурсы.
Семантика распространения и отмены исключений Исключение CancellationException относится к особым случаям и не обрабатывается как обычные сбои. Другие исключения всплывают в иерархии сопрограмм, отменяя дочерние и родительские функции до тех пор, пока не будет достигнут корень. Этот эффект домино обеспечивает согласованную обработку сбоев. Пользовательские обработчики могут переопределять значения по умолчанию, когда это необходимо.
Последовательный по умолчанию для обеспечения предсказуемости Функции приостановки выполняются одна за другой, если не указано иное. Две задачи приостановки продолжительностью в одну секунду занимают примерно две секунды, если они составлены последовательно. Это отражает интуитивно понятный императивный поток. Параллелизм должен быть запрошен явно.
Параллелизм с асинхронностью и ожиданием асинхронность создает отложенный результат, представляющий собой неблокирующий будущий результат. Ожидание нескольких отложений позволяет задачам выполняться одновременно, что часто сокращает общее время выполнения до самой медленной задачи. Если одна из задач завершается неудачей, другие отменяются, и ошибка распространяется. Эта схема кажется знакомой разработчикам, работающим на JavaScript.
Набор инструментов библиотеки сопрограмм Библиотека сопрограмм Kotlin находится на уровне suspend, обеспечивая сборщики, диспетчеры и структурированный параллелизм. Каналы, участники и select предлагают шаблоны синхронизации, вдохновленные Go. Модули, зависящие от платформы, адаптируют поведение для каждой цели. Исходный код раскрывает внутренние компоненты, такие как реализации dispatcher, для более глубокого изучения.
Где узнать подробности Официальная документация Kotlin и раздел "сопрограммы" дают представление о концепциях и API-интерфейсах. В главе спецификации языка, посвященной асинхронной механике и механике сопрограмм, подробно описываются продолжения, перехватчики и конечные автоматы. В русскоязычной книге Kotlin cookbook упоминаются основы сопрограмм. В репозитории GitHub хранятся исходные тексты и дополнительная документация для изучения.