Рутины к Корутинам: как Котлин произвел революцию в асинхронном программировании
· 4 мин чтения · 30 марта 2025
Основы: Главная рутина и подпрограммы
В традиционном программировании поток выполнения строится вокруг главной рутины (точки входа в программу) и подпрограмм (вспомогательных функций, выполняющих конкретные задачи).
Что такое главная рутина?
Главная рутина (часто называемая main()) — это:
• Точка входа каждой программы
• Первая функция, выполняемая при запуске кода
• Организует рабочий процесс программы
• В Kotlin/Java/C-подобных языках выглядит так:
fun main() {
// Программа начинает выполнение здесь
first()
second()
}
1. Обязательна — Каждая исполняемая программа должна иметь её
2. Родительская функция — Вызывает другие подпрограммы
3. Контроллер жизненного цикла — Определяет порядок операций
Что такое подпрограммы?
Подпрограммы (также называемые функциями/методами) — это:
• Повторно используемые блоки кода для конкретных задач
• Вызываются главной рутиной или другими подпрограммами
• Модулируют код в логические единицы
fun first() {
// Выполняет задачу A
}
fun second() {
// Выполняет задачу B
}
1. Единая ответственность — Каждая должна делать одну вещь хорошо
2. Вызываемость — Может запускаться многократно
3. Основаны на стеке — Следуют принципу LIFO (последним вошёл — первым вышел)
Пример с блокировкой
fun main() {
first() // подпрограмма
second() // подпрограмма
}
fun first() {
var first = 1
while (true) {
println("first: ${first++}")
}
}
fun second() {
var second = 1
while (true) {
println("second: ${second++}")
}
}
1. Программа запускается → main() начинает выполняться
2. main() вызывает first() → выполнение переходит к first()
3. first() входит в бесконечный цикл → никогда не возвращается
4. second() никогда не вызывается, потому что управление не возвращается в main()
Критическое ограничение
Это демонстрирует фундаментальное ограничение программирования:
• Подпрограммы блокируют — Вызывающий ждёт завершения
• Нет параллелизма — В любой момент выполняется только одна функция
• Зависит от порядка — Строго последовательное выполнение
Визуализация стека вызовов
Такой блокирующий подход решается корутинами, которые предоставляют:
Решение с корутинами
fun main(): Unit = runBlocking {
launch { first() } // корутина 1
launch { second() } // корутина 2
}
suspend fun first() {
var first = 1
while (true) {
println("first: ${first++}")
delay(2000) // Точка приостановки
}
}
suspend fun second() {
var second = 1
while (true) {
println("second: ${second++}")
delay(1000) // Точка приостановки
}
}
Ключевые отличия от подпрограмм
1. Неблокирующее параллельное выполнение
• Обе функции теперь выполняются параллельно, а не последовательно
• Вывод будет чередоваться: «second» печатается в два раза чаще, чем «first»
• Ни одна функция не блокирует другую
2. Магия точек приостановки
• delay() — это функция приостановки, которая останавливает выполнение без блокировки
• При встрече такой функции корутина:
◦ Позволяет выполняться другим корутинам
3. Структурированный параллелизм
• runBlocking создаёт область видимости для корутин
• launch запускает новые корутины как дочерние элементы этой области
• Все корутины автоматически отменяются при завершении области видимости
Внутренний механизм
Хронология выполнения корутин
Почему это важно?
› Традиционные потоки: ~1 МБ стека на поток
› Корутины: ~50 байт на приостановленную корутину
› Последовательно выглядящий код с асинхронным выполнением
› Главный поток никогда не блокируется
› Параллельные сетевые запросы
Визуальное сравнение
Выполнение подпрограмм
main()
└── first() (выполняется вечно)
└── second() (никогда не достигается)
Выполнение корутин
runBlocking
├── launch { first() } (приостанавливается/возобновляется)
└── launch { second() } (приостанавливается/возобновляется)
Заключение
Это демонстрирует смену парадигмы от линейного, блокирующего выполнения к гибкому, неблокирующему параллелизму. Используя точки приостановки и структурированный параллелизм, корутины позволяют эффективно выполнять многозадачность, сохраняя ясность кода. Эта эволюция от жёсткого выполнения подпрограмм к гибким корутинам представляет собой фундаментальный прорыв в асинхронном программировании.
Надеюсь, статья вам понравилась. Удачного кодинга!
Опубликовано в ProAndroidDev