← Назад к вопросам

Как корутины устроены под капотом

3.0 Senior🔥 181 комментариев
#JVM и память#Kotlin основы#Многопоточность и асинхронность

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Как устроены корутины под капотом

Это глубокий вопрос о внутреннем механизме Kotlin корутин. Корутины — это не потоки, а совсем другой механизм выполнения кода.

Основная идея: State Machine

Каждая корутина компилятор преобразует в State Machine (автомат с состояниями). Это ключ к пониманию.

// Исходный код (Kotlin)
suspend fun loadData(): String {
    val data = fetchFromNetwork()  // suspend point 1
    val processed = process(data)  // suspend point 2
    return processed
}

// Компилятор создает:
class LoadDataContinuation(val completion: Continuation<String>) : Continuation<String> {
    var state = 0
    var result: String? = null
    
    override fun resumeWith(result: Result<String>) {
        when (state) {
            0 -> {
                state = 1
                fetchFromNetwork().invokeOnCompletion { data ->
                    this.result = data
                    resumeWith(Result.success(data))
                }
            }
            1 -> {
                state = 2
                val processed = process(this.result!!)
                resumeWith(Result.success(processed))
            }
            2 -> {
                completion.resumeWith(Result.success(this.result!!))
            }
        }
    }
}

Continuation: Основной интерфейс

Continuation — это объект, который содержит код, который нужно выполнить дальше.

public interface Continuation<in T> {
    public val context: CoroutineContext
    public fun resumeWith(result: Result<T>)
}

// Расширения
public fun <T> Continuation<T>.resume(value: T): Unit =
    resumeWith(Result.success(value))

public fun <T> Continuation<T>.resumeWithException(exception: Throwable): Unit =
    resumeWith(Result.failure(exception))

Suspend функции: трансформация

Компилятор преобразует suspend функции:

// Исходная suspend функция
suspend fun delay(time: Long)

// Компилятор создает обычную функцию с Continuation параметром
fun delay(time: Long, continuation: Continuation<Unit>): Any

Magic return value: COROUTINE_SUSPENDED

// Suspend функция может вернуть:
// 1. Результат (если не нужна приостановка)
// 2. COROUTINE_SUSPENDED (если приостановлена)

public val COROUTINE_SUSPENDED: Any = object {}

suspend fun fetchData(): String = suspendCancellableCoroutine { cont ->
    networkCall { result ->
        cont.resume(result)  // Возобновить корутину
    }
    COROUTINE_SUSPENDED  // Вернуть this value
}

Dispatcher: Где выполняется код

Корутины не привязаны к потокам. Dispatcher решает где выполнить код:

// Dispatcher определяет поток
launchIO {
    // Выполняется на IO потоке
    val data = fetchFromNetwork()  // Network IO
    
    withContext(Dispatchers.Main) {
        // Переключились на Main поток
        updateUI(data)
    }
    
    withContext(Dispatchers.Default) {
        // Выполняется на Default потоке
        heavyComputation()
    }
}

Типы Dispatcher'ов:

Main       - UI поток (один поток для UI)
IO         - Thread pool для IO операций (64 потока по умолчанию)
Default    - Thread pool для CPU-intensive (ядра процессора)
Unconfined - Не переключает потоки (опасно!)
Custom     - Свой Executor

Как работает withContext

launchIO {
    println("1: ${Thread.currentThread().name}")  // IO-1
    
    withContext(Dispatchers.Main) {
        println("2: ${Thread.currentThread().name}")  // main
    }
    
    println("3: ${Thread.currentThread().name}")  // IO-1 (вернулись обратно!)
}

Внутренний механизм:

  1. Корутина приостанавливается в withContext
  2. Код в блоке выполняется на Main диспетчере
  3. Сохраняется Continuation с информацией о возврате
  4. После завершения блока, Continuation возобновляется на IO диспетчере

Scope: Иерархия корутин

CoroutineScope организует иерархию и управление жизненным циклом:

class MyViewModel : ViewModel() {
    // viewModelScope автоматически отменяется при очистке ViewModel
    fun loadData() {
        viewModelScope.launch {
            val result = fetchData()  // Отменится при очистке
        }
    }
}

// Структурированная параллелизм
launch {
    val job1 = launch { task1() }
    val job2 = launch { task2() }
    // Ждёт завершения job1 и job2 перед выходом из launch блока
}

Job и Cancellation

Job отслеживает состояние корутины:

val job = launch {
    try {
        delay(5000)
        println("Done")
    } catch (e: CancellationException) {
        println("Cancelled")
        throw e  // Переброс обязателен!
    }
}

job.cancel()  // Отменить корутину
job.join()    // Ждать отмены

// Или вместе
job.cancelAndJoin()

Механизм отмены:

  1. cancel() устанавливает флаг отмены
  2. При следующей suspend точке выбрасывается CancellationException
  3. Finally блоки выполняются
  4. Корутина завершается

Как работает delay

// delay — это suspend функция
suspend fun delay(timeMillis: Long)

// Под капотом (упрощенно):
fun delay(timeMillis: Long, continuation: Continuation<Unit>): Any {
    val scheduler = continuation.context[CoroutineDispatcher]
    scheduler.scheduleBlock(timeMillis) {
        continuation.resume(Unit)  // Возобновить корутину
    }
    return COROUTINE_SUSPENDED  // Приостановить
}

CoroutineContext: Управление контекстом

// CoroutineContext содержит информацию о корутине
val context: CoroutineContext = Dispatchers.Main + Job() + CoroutineExceptionHandler { _, e ->
    println("Error: $e")
}

// Элементы контекста
Dispatchers.Main         // Где выполнять
Job()                    // Управление жизненным циклом
CoroutineExceptionHandler // Обработка ошибок
CoroutineName           // Имя для отладки

Практический пример: Трансформация

// Исходный код
suspend fun getUserAndPosts(userId: String): Pair<User, List<Post>> {
    val user = fetchUser(userId)      // suspend point
    val posts = fetchPosts(userId)    // suspend point
    return user to posts
}

// Компилятор создает State Machine
fun getUserAndPosts(
    userId: String,
    completion: Continuation<Pair<User, List<Post>>>
): Any {
    var state = 0
    var user: User? = null
    var posts: List<Post>? = null
    
    fun resume() {
        try {
            when (state) {
                0 -> {
                    state = 1
                    fetchUser(userId) { result ->
                        user = result
                        resume()
                    }
                    return COROUTINE_SUSPENDED
                }
                1 -> {
                    state = 2
                    fetchPosts(userId) { result ->
                        posts = result
                        resume()
                    }
                    return COROUTINE_SUSPENDED
                }
                2 -> {
                    completion.resume(user!! to posts!!)
                }
            }
        } catch (e: Throwable) {
            completion.resumeWithException(e)
        }
    }
    
    return resume()
}

Производительность

Почему корутины быстрые:

  • Не потоки - Миллионы корутин на одном потоке
  • Stack-less - Не используют стек для каждой корутины
  • Cheap switching - Переключение между корутинами дешево
  • No context switch - Нет overhead CPU переключения контекста
Потоки:     1000 потоков = ~1GB памяти
Корутины:   1М корутин = ~100MB памяти

Вывод

Корутины под капотом:

  1. State Machine - Компилятор преобразует в автомат состояний
  2. Continuation - Объект с информацией о продолжении
  3. Dispatcher - Определяет поток выполнения
  4. Job - Управляет жизненным циклом
  5. COROUTINE_SUSPENDED - Магический return value
  6. No threads - Не привязаны к потокам

Этот механизм позволяет писать асинхронный код как синхронный, без callback hell.