Что такое Корутины в Котлине?


Корутины — это отличный функционал, доступный в языке Kotlin. Я уже опробовал его и мне он очень понравился.


Цель этой статьи — помочь вам понять Корутины. Просто будьте внимательны при прочтении и у вас всё получится.


Начнем с официального определения Корутин.


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

Первый вопрос, возникающий при прочтении этого определения — чем Корутины отличаются от потоков?


Корутины — это облегчённые потоки. Облегчённый поток означает, что он не привязан к нативному потоку, поэтому он не требует переключения контекста на процессор, поэтому он быстрее.


Что это значит, «не привязан к нативному потоку»?


Корутины есть во многих языках программирования.


В принципе, есть два типа Корутин:


  • использующие стек;
  • неиспользующиие стек;

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


И Корутины, и потоки являются многозадачными. Но разница в том, что потоки управляются ОС, а Корутины пользователями.


Теперь вы можете осознанно прочитать и понять выдержку с официального сайта Kotlin:


Корутины можно представить в виде облегчённого потока. Подобно потокам, корутины могут работать параллельно, ждать друг друга и общаться. Самое большое различие заключается в том, что корутины очень дешевые, почти бесплатные: мы можем создавать их тысячами и платить очень мало с точки зрения производительности. Потоки же обходятся дорого. Тысяча потоков может стать серьезной проблемой даже для современной машины.

Давайте посмотрим, как работать с Корутинами


Итак, как запустить корутину (по аналогии с запуском потока)?


Есть две функции для запуска корутины:


  • launch{}
  • async{}

launch{} vs async{} в Корутинах Kotlin


Разница в том, что launch{} ничего не возвращает, а async{} возвращает экземпляр Deferred, в котором имеется функция await(), которая возвращает результат корутины, прямо как Future в Java, где мы делаем future.get() для получения результата.


Давайте посмотрим на использование launch{}


fun main(args: Array<String>) {
    println("Kotlin Start")
    launch(CommonPool) {
        delay(2000)
        println("Kotlin Inside")
    }
    println("Kotlin End")
}

// The output will be
// Kotlin Start
// Kotlin End
// Kotlin Inside

Этот код запустит новую корутину в данном пуле потоков. В этом случае мы используем CommonPool, который использует ForkJoinPool.commonPool(). Потоки все ещё существуют в программе, основанной на корутинах, но один поток может запускать много корутин, поэтому нет необходимости в слишком большом количестве потоков.


Попробуем одну вещь:


fun main(args: Array<String>) {
    delay(2000)
}

Если вы cделаете это прямо в основной функции, то получите сообщение об ошибке:


Функции прерывания можно вызвать только из корутины или другой функции прерывания.


Функция задержки является функцией прерывания, соответственно мы можем ее вызывать только из корутины или другой функции прерывания.


Давайте исправим это:


fun main(args: Array<String>) {
    runBlocking {
        delay(2000)
    }
}

Ещё один пример:


suspend fun doWorkFor1Seconds(): String {
    delay(1000)
    return "doWorkFor1Seconds"
}

suspend fun doWorkFor2Seconds(): String {
    delay(2000)
    return "doWorkFor2Seconds"
}

// Serial execution 
private fun doWorksInSeries() {
    launch(CommonPool) {
        val one = doWorkFor1Seconds()
        val two = doWorkFor2Seconds()
        println("Kotlin One : " + one)
        println("Kotlin Two : " + two)
    }
}

// The output is
// Kotlin One : doWorkFor1Seconds
// Kotlin Two : doWorkFor2Seconds

Теперь посмотрим на использование async{}


// Parallel execution
private fun doWorksInParallel() {
    val one = async(CommonPool) {
        doWorkFor1Seconds()
    }
    val two = async(CommonPool) {
        doWorkFor2Seconds()
    }
    launch(CommonPool) {
        val combined = one.await() + "_" + two.await()
        println("Kotlin Combined : " + combined)
    }
}
// The output is
// Kotlin Combined : doWorkFor1Seconds_doWorkFor2Seconds

Т.к. мы используем async{}, то можем вызвать await() для получения результата.


Счастливого обучения ;)

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


  1. KoToSveen
    29.08.2018 07:34

    Я не владыка корутин в Котлине, мне просто интересно почему минусуют эту статью?


    1. AxisPod
      29.08.2018 08:09
      +2

      Я не знаю Kotlin вообще. Но далее следует общее опредление, свойственное всем известным мне реализациям.

      Видимо потому что автор вообще не знает разницу между асинхронным и многопоточным кодом.

      Видимо потому что корутины не являются потоками, от слова вообще. Корутины — синтаксический сахар. Корутины не являются многозадачными, вообще и никак. Корутины исполняются строго в вызываемом потоке, просто асинхронно и не более. Они не могут вызываться в других потоках, чтобы избежать проблем с хранимыми данными на стеке, которые захватываются контекстом корутин.

      Как оно выглядит для компилятора: код корутин разворачивается во время компиляции на несколько отдельных блоков кода. Которые будут вызваны по мере выполнения асинхронных задач. При этом сохраняется последовательность кода. Просто за программиста убирается ад колбэков.


      1. cher11
        29.08.2018 16:41

        Можно уточнить, а в каком потоке выполняются сами асинхронные задачи? Над ней работает другой поток, который и занимается задачей, затем дергается callback в исходный? Или как это вообще работает?
        Вот, к примеру, тот же JS и сетевой запрос с коллбеком. При отправке запроса UI не тормозит, потому что единственный поток свободен и готов его обновлять. Но где крутится сам запрос?


        1. AxisPod
          29.08.2018 16:55
          +1

          Конкретно работа с сетью, с файлами находится в недрах реализации мультиплексирования неблокируемого ввода-вывода, этим по сути заведует ОС. Подробнее можно поглядеть для win — IOCP, для Linux — epoll, для BSD — kqueue, либо библиотеки более высокого уровня — libuv, libev, libevent, boost::asio.

          Где-то за кулисами может и быть и ожидание в отдельном потоке (на зачастую это не самое хорошее решение), либо одним потоком можно опрашивать состояние большого кол-ва задач (получше, но всё же всё равно плохо).


          1. cher11
            29.08.2018 23:39

            Спасибо. Другими словами, все-таки можно сказать, что хоть и в рамках языка программирования мы и говорим, что все в одном потоке, но при использовании async/await «за кулисами» в любом случае идет какая-то фоновая работа (причем нам даже не всегда известно, как именно это реализовано).
            Раньше очень смущала такая формулировка, т.к. в любом случае очевидно, что для того, чтобы любую операцию выполнить асинхронно, мы все равно должны эту асинхронную работу где-то выполнить.


      1. Deterok
        31.08.2018 09:24
        +1

        Так это вообще перевод. Странно его минусовать за мысли автора…


        1. AxisPod
          31.08.2018 09:28

          Ну перевод-переводом, но я думаю, что надо всё же читать, что переводишь. И соответственно не переводить такого рода бред.


    1. Lietto
      29.08.2018 13:03

      Что статья что перевод — статья ради статьи. Неинформативна совсем.


  1. valeriyvan
    29.08.2018 09:37

    > И Корутины, и потоки являются многозадачными.
    LOL


  1. SirEdvin
    29.08.2018 09:48

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

    Не совсем понимаю, что тут имеется ввиду. Если у корутин нет своего стека, разве это не значит, что они используют стек потока, в котором выполняются, а значит, они привязаны таки к потоку?


    1. Prototik
      29.08.2018 20:46

      Используют, но в момент suspend'инга корутина сохраняет весь свой стек в объект и разворачивает его обратно в момент resume. Поэтому корутинам и не важно, в каком потоке они выполняются — всё их состояние хранится в обычном объекте.


  1. myemuk
    29.08.2018 11:44
    +1

    Просто оставлю здесь: Статья


    1. AxisPod
      29.08.2018 12:14

      Ха, Альфа-Банк тоже не знает разницу между асинхронностью и многопоточностью. По крайней мере по началу статьи.