Корутины (coroutines), или сопрограммы — это блоки кода, которые работают асинхронно, то есть по очереди. В нужный момент исполнение такого блока приостанавливается с сохранением всех его свойств, чтобы запустился другой код. Когда управление возвращается к первому блоку, он продолжает работу. В результате программа выполняет несколько функций одновременно.
Для чего нужны корутины
- Для создания асинхронных приложений, которые могут выполнять несколько действий одновременно.
- Для гибкой и удобной реализации многозадачности.
- Для большего контроля при переключении между разными задачами. Корутинами управляют разработчик и программа, а не операционная система.
- Для снижения нагрузки на аппаратные ресурсы устройства.
Корутины поддерживают Kotlin, JavaScript, PHP, C#, Go, Python, Ruby, Lua и другие языки программирования.
Как работают сопрограммы
Сопрограммы — это потоки исполнения кода, которые организуются поверх системных потоков. Поток исполнения кода — это последовательность операций, выполняющихся друг за другом. Она исполняется, пока не наступит момент, который задан в коде или определен разработчиком. Затем может начать выполняться часть другой последовательности операций. В системных потоках содержатся инструкции процессора, на одном его ядре могут по очереди работать разные системные потоки. Корутины работают на более высоком уровне: несколько сопрограмм могут поочередно выполнять свой код на одном системном потоке.
Для конечного пользователя работа корутин выглядит как несколько действий, которые выполняются параллельно.
Принципы работы корутин:
- Могут иметь несколько точек для входа и возврата.
- Приостанавливаются и возобновляют работу больше одного раза.
- Действуют по стратегии LIFO — последняя вызванная корутина закончится первой.
Примеры использования корутин в Java и Kotlin
Пример ниже создает две корутины в Java: одну для выполнения в фоновом потоке и другую для выполнения в основном потоке. Корутины выполняются асинхронно, и можно увидеть, как они и основной поток выполняются параллельно:
import kotlinx.coroutines.CoroutineScope; import kotlinx.coroutines.Dispatchers; import kotlinx.coroutines.launch; public class Main { public static void main(String[] args) { // Создаем область корутин CoroutineScope scope = CoroutineScope(Dispatchers.Default); // Запускаем корутину scope.launch(() -> { // Код, выполняющийся в корутине for (int i = 0; i < 5; i++) { System.out.println("Корутина: " + i); try { Thread.sleep(1000); // Имитация работы } catch (InterruptedException e) { e.printStackTrace(); } } }); // Код основного потока for (int i = 0; i < 5; i++) { System.out.println("Основной поток: " + i); try { Thread.sleep(1000); // Имитация работы } catch (InterruptedException e) { e.printStackTrace(); } } // Ожидаем завершения всех корутин scope.close(); } }
А в этом примере для Kotlin создается корутина с помощью функции launch
из библиотеки kotlinx.coroutines и в ней выполняется асинхронная работа с использованием функции delay
. Затем приостанавливается основной поток с помощью Thread.sleep
, чтобы дать корутине время выполнить свою работу:
import kotlinx.coroutines.* fun main() { // Запускаем корутину в главном потоке GlobalScope.launch { println("Корутина: Начало работы") // Приостанавливаем корутину на 1 секунду delay(1000) println("Корутина: Прошло 1 секунда") // Приостанавливаем корутину еще на 1 секунду delay(1000) println("Корутина: Прошло еще 1 секунда") // Завершаем корутину println("Корутина: Завершение работы") } // Код основного потока println("Основной поток: Начало работы") // Приостанавливаем основной поток на 2 секунды Thread.sleep(2000) println("Основной поток: Прошло 2 секунды") // Основной поток завершается, но корутина продолжает работать // Завершаем корутину после завершения основного потока println("Основной поток: Завершение работы") }
Обратите внимание, что корутина продолжает работать даже после завершения основного потока.
Различия между корутинами и потоками
Многозадачность с использованием корутин легко перепутать с многопоточностью. Это выполнение программы в нескольких системных потоках.
Поток — составная часть процесса, который выполняется в операционной системе. Принцип похож: несколько потоков останавливаются и возобновляются, ждут друг друга, общаются. Но есть отличия.
- Потоками управляет операционная система. Переключением корутин — разработчик с помощью кода.
- Переключение потоков сложно контролировать. Корутины контролируются более гибко.
- Потоки отнимают много ресурсов процессора — ему постоянно приходится переключаться между ними. Корутины не требуют переключения контекста, поэтому код потребляет мало ресурсов.
- Потоки выполняются на аппаратном или системном уровне. Корутины — более высокоуровневое решение. Это значит, что они дальше от системы и аппаратных ресурсов, зато ближе к человеческим понятиям.
- Потоки ускоряют выполнение сложной задачи, но отнимают много ресурсов. Корутины не повышают скорость, но помогают оптимизировать нагрузку.
- Корутины выполняются в рамках одного потока или пула потоков.
Преимущества использования корутин
Эффективность
Многозадачная программа эффективнее расходует ресурсы. Основной код не блокируется, чтобы мог выполниться вспомогательный модуль. Вместо этого они работают асинхронно.
Удобство для пользователя
Корутины переключаются быстро, для пользователя это выглядит как одновременное выполнение задач. Ему не приходится подолгу ждать ответа программы. Он продолжает работать с ней, пока асинхронные корутины незаметно для него выполняют дополнительные действия.
Снижение нагрузки на систему
Асинхронность позволяет выполнять несколько действий в рамках одного потока вместо того, чтобы множить потоки. Системе проще выполнить один поток, чем несколько. Разработчики из JetBrains прояснили это на таком примере: запустить 10 тысяч корутин одновременно — задача, с которой устройство справится. А 10 тысяч потоков — невозможное явление: компьютеру не хватит памяти.
Гибкость в управлении
Переключение между корутинами происходит вручную. Программист сам прописывает этот момент в коде. Поэтому управление корутинами можно контролировать, что дает разработчику больше возможностей. Нет риска, что программа переключится между блоками кода в неподходящий момент, как это бывает с менее гибкими решениями.
Недостатки сопрограмм
Сложность и высокий порог вхождения
Разобраться в работе корутин и научиться писать асинхронный код может быть сложно. Поэтому сопрограммы начинают изучать специалисты, которые уже хорошо разобрались в базовых принципах выбранного языка.
Узкая специализация
Корутины — высокоуровневое решение. Ускорить сложные вычисления с помощью сопрограмм не получится. В этом случае нужна многопоточность, а не асинхронность. При этом корутины подходят для снижения нагрузки на систему.
Отсутствие в ряде языков
Корутины трудно реализовать в языках, которые их не поддерживают. Для этого нужно использовать сторонние решения или преобразовывать код на одном языке в другой. Это усложняет разработку.
0 комментариев