Подтвердить что ты не робот

Разница между нитью и сопрограммой в Котлине

Есть ли какая-либо конкретная языковая реализация в Котлин, которая отличается от других языков?

  • Что означает, что сопрограмма похожа на легкую нить?
  • В чем разница?
  • Выполняются ли параллельные/параллельные транзакции kotlin coroutines?
  • Даже в многоядерной системе в любой момент времени работает только одна сопрограмма (правильно?)

Здесь я запускаю 100000 сопрограмм, что происходит за этим кодом?

for(i in 0..100000){
   async(CommonPool){
    //run long running operations
  }
}
4b9b3361

Ответ 1

Поскольку я использовал сопрограммы только на JVM, я буду говорить о бэкэнде JVM, есть также Kotlin Native и Kotlin JavaScript, но это поддерживает для Kotlin из моего объема.

Итак, давайте начнем с сопоставления сопрограмм Kotlin с другими языковыми сопрограммами. В основном вы должны знать, что это два типа Coroutines: без штабелей и стеков. Kotlin реализует стекированные сопрограммы - это означает, что coroutine не имеет собственного стека, и это ограничивает немного, что может сделать coroutine. Вы можете прочитать хорошее объяснение здесь.

Примеры:

  • Stackless: С#, Scala, Kotlin
  • Стоки: Квазар, Джавафлоу

Что означает, что сопрограмма похожа на легкую нить?

Это означает, что coroutine в Kotlin не имеет собственного стека, он не отображается на собственный поток, он не требует переключения контекста на процессор.

В чем разница?

Thread - упреждающая многозадачность. (обычно). Coroutine - совместная многозадачность.

Thread - управляется ОС (обычно). Coroutine - управляется пользователем.

Действительно ли выполняются параллельные/параллельные сопрограммы kotlin?

Это зависит, вы можете запускать каждую сопрограмму в собственном потоке или вы можете запускать все сопрограммы в одном потоке или в некотором фиксированном пуле потоков.

Подробнее о том, как coroutines выполняет здесь.

Даже в многоядерной системе есть только одна сопрограмма, выполняемая в любой момент времени (правильно?)

Нет, см. предыдущий ответ.

Здесь я запускаю 100000 сопрограмм, что происходит за этим кодом?

На самом деле это зависит. Но предположим, что вы пишете следующий код:

fun main(args: Array<String>) {
    for (i in 0..100000) {
        async(CommonPool) {
            delay(1000)
        }
    }
}

Этот код выполняется мгновенно.

Поскольку нам нужно ждать результатов вызова async.

Итак, исправьте это:

fun main(args: Array<String>) = runBlocking {
    for (i in 0..100000) {
        val job = async(CommonPool) {
            delay(1)
            println(i)
        }

        job.join()
    }
}

Когда вы запустите эту программу, kotlin создаст 2 * 100000 экземпляров Continuation, на которые потребуется несколько десятков Мб ОЗУ, а в консоли вы увидите цифры от 1 до 100000.

Итак, переписываем этот код следующим образом:

fun main(args: Array<String>) = runBlocking {

    val job = async(CommonPool) {
        for (i in 0..100000) {
            delay(1)
            println(i)
        }
    }

    job.join()
}

Что мы достигли сейчас? Теперь мы создаем только 100001 экземпляров Continuation, и это намного лучше.

Каждое созданное продолжение будет отправлено и выполнено на CommonPool (который является статическим экземпляром ForkJoinPool).

Ответ 2

Что означает, что сопрограмма похожа на легкую нить?

Coroutine, как поток, представляет последовательность действий, которые выполняются одновременно с другими сопрограммами (потоками).

В чем разница?

Нить напрямую связана с собственным потоком в соответствующей ОС (операционной системе) и потребляет значительное количество ресурсов. В частности, он потребляет много памяти для своего стека. Вот почему вы не можете просто создавать потоки 100k. Вероятно, у вас не хватает памяти. Переключение между потоками связано с диспетчером ядра ОС, и это довольно дорогостоящая операция с точки зрения потребления циклов процессора.

С другой стороны, сопрограмма - это чисто абстракция языка на уровне пользователя. Он не связывает никаких собственных ресурсов и в простейшем случае использует только один относительно небольшой объект в куче JVM. Вот почему легко создавать 100 тыс. Сопрограмм. Переключение между сопрограммами не связано с ядром ОС. Это может быть так же дешево, как вызывать регулярную функцию.

Являются ли kotlin сопрограммы фактически запущенными параллельно/одновременно? Даже в многоядерной системе в любой момент времени работает только одна сопрограмма (правильно?)

Контур может быть запущен или приостановлен. Подстановленный сопрограмм не связан ни с какими конкретными потоками, но работающий сопрограмм работает на каком-то потоке (использование потока - единственный способ выполнить что-либо внутри процесса ОС). Независимо от того, работают ли разные сопрограммы в одном потоке (таким образом, можно использовать только один процессор в многоядерной системе) или в разных потоках (и, следовательно, могут использовать несколько процессоров), это находится исключительно в руках программиста, который использует сопрограммы.

В Котлине диспетчеризация сопрограмм контролируется через контекст coroutine. Вы можете больше узнать об этом в Руководство по kotlinx.coroutines

Здесь я запускаю 100000 сопрограмм, что происходит за этим кодом?

Предполагая, что вы используете функцию launch и CommonPool из проекта kotlinx.coroutines (который является открытым исходным кодом), вы можете изучить их исходный код здесь:

launch просто создает новую сопрограмму, а CommonPool отправляет сопрограммы в ForkJoinPool.commonPool(), который использует несколько потоков и, таким образом, выполняет на нескольких процессорах в этом примере.

Код, который следует за вызовом launch в {...}, называется приостановкой лямбда. Что это такое и как приостановить lambdas и функции, реализованные (скомпилированные), а также стандартные библиотечные функции и классы, такие как startCoroutines, suspendCoroutine и CoroutineContext, объясняются в соответствующем Kotlin coroutines design document.