Kotlin中线程和协程之间的区别

时间:2017-03-25 20:50:57

标签: kotlin kotlin-coroutines

Kotlin中是否有任何特定的语言实现,这与协程的其他语言实现有所不同?

  • 什么意味着协程就像轻量级线程?
  • 有什么区别?
  • kotlin协程实际上是并行/同时运行吗?
  • 即使在多核系统中,任何时候都只有一个协程运行(是不是?)

这里我开始使用100000个协程,这段代码背后会发生什么?

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

2 个答案:

答案 0 :(得分:47)

  

什么意味着协程就像轻量级的线程?

Coroutine就像一个线程一样,代表了一系列与其他协同程序(线程)同时执行的动作。

  

有什么区别?

线程直接链接到相应OS(操作系统)中的本机线程,并消耗大量资源。特别是,它为其堆栈消耗了大量内存。这就是为什么你不能只创建100k线程。你可能会耗尽内存。线程之间的切换涉及操作系统内核调度程序,就消耗的CPU周期而言,这是一项相当昂贵的操作。

另一方面,协程纯粹是用户级语言抽象。它不会绑定任何本机资源,并且在最简单的情况下,它只使用JVM堆中的一个相对较小的对象。这就是为什么很容易创建100k协同程序。在协程之间切换根本不涉及OS内核。它可以像调用常规函数一样便宜。

  

kotlin的协程实际上并行/同时运行吗?   即使在多核系统中,在任何给定时间只有一个协程运行(是不是?)

协程可以正在运行或暂停。挂起的协程没有与任何特定线程相关联,但是正在运行的协程在某个线程上运行(使用线程是在OS进程内执行任何操作的唯一方法)。是否所有运行在同一线程上的协同程序(因此可能只在多核系统中使用单个CPU)或在不同的线程中运行(因此可能使用多个CPU)完全由使用协同程序的程序员掌握。

在Kotlin中,通过 coroutine context 来控制协同程序的调度。你可以在这里阅读更多关于 Guide to kotlinx.coroutines

  

这里我开始使用100000个协程,这个代码背后会发生什么?

假设您正在使用launch项目(开源)中的CommonPool函数和kotlinx.coroutines上下文,您可以在此处查看其源代码:

launch只创建新的协程,而CommonPool将协同程序发送到ForkJoinPool.commonPool(),它使用多个线程,因此在此示例中在多个CPU上执行。

launch{...}次调用之后的代码称为暂停lambda 。它是什么以及如何实现(编译)挂起lambdas和函数以及如startCoroutinessuspendCoroutineCoroutineContext之类的标准库函数和类在相应的Kotlin coroutines design document中解释

答案 1 :(得分:40)

由于我只在JVM上使用协同程序,我将讨论JVM后端,还有Kotlin Native和Kotlin JavaScript,但Kotlin的这些后端超出了我的范围。

因此,让我们首先将Kotlin协程与其他语言协程进行比较。基本上,你应该知道有两种类型的协同程序:无堆栈和堆栈。 Kotlin实现无堆栈协程 - 这意味着协程没有自己的堆栈,这限制了协程可以做的一点点。你可以阅读一个很好的解释here

示例:

  • Stackless:C#,Scala,Kotlin
  • Stackful:Quasar,Javaflow
  

coroutine就像轻量级线程一样意味着什么?

这意味着Kotlin中的协程没有自己的堆栈,它不会在本机线程上映射,也不需要在处理器上进行上下文切换。

  

有什么区别?

线程 - 抢先式多任务处理。 (usually)。 协同 - 合作多任务。

线程 - 由OS(通常)管理。 协同程序 - 由用户管理。

  

kotlin的协同程序是否实际并行/同时运行?

这取决于你可以在自己的线程中运行每个协同程序,或者你可以在一个线程或一些固定的线程池中运行所有协同程序。

有关协程如何执行here的更多信息。

  

即使在多核系统中,任何时候都只有一个协同程序在运行(是不是?)

不,请参阅上一个答案。

  

在这里,我启动了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实例,这将占用几十Mb的RAM,在控制台中,您将看到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个实例,这要好得多。

每个创建的Continuation都将在CommonPool(这是ForkJoinPool的静态实例)上调度和执行。