繁体   English   中英

Kotlin 中线程和协程的区别

[英]Difference between thread and coroutine in Kotlin

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

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

这里我启动了 100000 个协程,这段代码背后发生了什么?

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

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

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

有什么不同?

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

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

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

协程可以运行或暂停。 挂起的协程与任何特定线程无关,但正在运行的协程在某个线程上运行(使用线程是在 OS 进程内执行任何内容的唯一方法)。 不同的协程是否都运行在同一个线程上(因此在多核系统中可能只使用一个 CPU)还是在不同的线程中(因此可能使用多个 CPU)完全取决于使用协程的程序员。

在 Kotlin 中,协程的调度是通过协程上下文控制的。 您可以在kotlinx.coroutines 指南中阅读更多相关信息

这里我启动了 100000 个协程,这段代码背后发生了什么?

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

launch只是创建新的协程,而CommonPool将协程分派到ForkJoinPool.commonPool() ,它确实使用多个线程,因此在本例中在多个 CPU 上执行。

{...} launch调用之后的代码称为挂起 lambda 它是什么以及如何实现(编译)挂起 lambda 和函数以及标准库函数和类,如startCoroutinessuspendCoroutineCoroutineContext ,在相应的Kotlin 协程设计文档中进行了解释。

因为我只在 JVM 上使用协程,所以我将讨论 JVM 后端,还有 Kotlin Native 和 Kotlin JavaScript,但这些 Kotlin 后端超出了我的范围。

所以让我们从比较 Kotlin 协程和其他语言的协程开始。 基本上,你应该知道有两种类型的 Coroutines:stackless 和 stackful。 Kotlin 实现了无堆栈协程——这意味着协程没有自己的堆栈,这限制了协程可以做什么。 你可以在这里阅读一个很好的解释。

例子:

  • 无堆栈:C#、Scala、Kotlin
  • Stackful:类星体、Javaflow

协程就像轻量级线程是什么意思?

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

有什么不同?

线程 - 抢先式多任务处理。 通常)。 协程 - 协同多任务处理。

线程 - 由操作系统管理(通常)。 协程 - 由用户管理。

kotlin 的协程实际上是并行/并发运行的吗?

这取决于,您可以在自己的线程中运行每个协程,也可以在一个线程或某个固定线程池中运行所有协程。

有关协程如何执行的更多信息,请参见此处

即使在多核系统中,在任何给定时间也只有一个协程在运行(对吗?)

不,请参阅上一个答案。

这里我启动了 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 的静态实例)上分派和执行。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM