【问题标题】:Difference between thread and coroutine in KotlinKotlin 中线程和协程的区别
【发布时间】:2017-08-18 17:01:09
【问题描述】:

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

  • 协程像轻量级线程是什么意思?
  • 有什么区别?
  • kotlin 协程实际上是并行/并发运行的吗?
  • 即使在多核系统中,任何时候也只有一个协程在运行(对吗?)

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

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

【问题讨论】:

标签: kotlin kotlin-coroutines


【解决方案1】:

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

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

例子:

  • 无堆栈:C#、Scala、Kotlin
  • 堆栈式:Quasar、Javaflow

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

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

有什么区别?

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

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

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 的静态实例)上调度和执行。

【讨论】:

  • 很好的答案,但我建议做一个重要的更正。 Kotlin 中的协程过去在最初的预发布预览中是无堆栈的,但实际上是在 Kotlin 1.1 中发布的,支持在任何堆栈深度暂停,例如在 Quasar 中。对于熟悉 Quasar 的人来说,很容易看出 Quasar 的 throws SuspendExecution 和 Kotlin 的 suspend 修饰符之间的一一对应关系。当然,实现细节有很大不同,但用户体验非常相似。
  • 也欢迎您在对应的design document中查看Kotlin协程的实际实现细节。
  • 坦率地说,我不知道术语“堆栈协同程序”是什么意思。我没有看到这个术语的任何正式/技术定义,而且我看到不同的人以完全矛盾的方式使用它。我会完全避免使用术语“堆栈协同程序”。我可以肯定地说,并且很容易验证的是,Kotlin 协程更接近 Quasar,并且与 C# 非常不同。无论您对“堆栈式协程”一词的特定定义如何,将 Kotlin 协程与 C# async 放在同一个 bin 中似乎都不正确。
  • 我会按照以下方式对各种语言中的协程进行分类:C#、JS 等具有基于未来/承诺的协程。这些语言中的任何异步计算都必须返回某种类似未来的对象。称它们为无堆栈是不公平的。你可以表达任何深度的异步计算,它只是在语法和实现方面效率低下。 Kotlin、Quasar 等都有基于暂停/延续的协程。它们严格来说更强大,因为它们可以与类似未来的对象一起使用,也可以不使用它们,单独使用挂起函数。
  • 好的。这是一篇很好的论文,它提供了协程的背景,并或多或少地给出了“堆栈式协程”的精确定义:inf.puc-rio.br/~roberto/docs/MCC15-04.pdf 这意味着 Kotlin 实现了堆栈式协程
【解决方案2】:

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

协程和线程一样,表示与其他协程(线程)同时执行的一系列动作。

有什么区别?

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

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

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

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

在 Kotlin 中,协程的调度是通过 协程上下文 控制的。您可以在 Guide to kotlinx.coroutines

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

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

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

{...}launch 调用之后的代码称为暂停 lambda。它是什么以及如何实现(编译)暂停 lambda 和函数以及标准库函数和类,如 startCoroutinessuspendCoroutineCoroutineContext,在相应的 Kotlin coroutines design document 中进行了说明。

【讨论】:

  • 那么粗略的说,是不是说启动一个协程类似于在线程队列由用户控制的线程队列中添加一个作业?
  • 是的。它可以是单个线程的队列,也可以是线程池的队列。您可以将协程视为更高级别的原语,让您避免手动(重新)将业务逻辑的延续提交到队列。
  • 所以这是否意味着当我们并行运行多个协同程序时,如果协同程序的数量远大于队列中线程的线程数,这不是真正的并行性?如果是这样的话,那么这听起来和Java的Executor很像,这两者有什么关系吗?
  • 这与线程没有什么不同。如果线程数大于物理核心数,则不是真正的并行性。不同之处在于线程抢先调度在核心上,而协程协同调度到线程上
猜你喜欢
  • 2010-12-28
  • 2020-04-26
  • 2019-11-13
  • 2020-07-15
  • 1970-01-01
  • 1970-01-01
  • 2020-07-19
  • 1970-01-01
相关资源
最近更新 更多