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


77

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

  • 什么是协程像轻质螺纹?
  • 有什么不同?
  • Kotlin协程是否实际上并行/并发运行?
  • 即使在多核系统中,在任何给定时间都只有一个协程运行(对吗?)

在这里,我要启动100000个协程,这段代码后面会发生什么?

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

Answers:


68

由于我仅在JVM上使用协程,因此我将讨论JVM后端,也有Kotlin Native和Kotlin JavaScript,但这些Kotlin后端不在我的讨论范围之内。

因此,让我们开始将Kotlin协程与其他语言的协程进行比较。基本上,您应该知道协程有两种类型:无堆栈和堆栈。Kotlin实现了无堆栈的协程-这意味着协程没有自己的堆栈,这限制了协程的功能。您可以在这里阅读很好的解释。

例子:

  • 无堆栈:C#,Scala,Kotlin
  • 大量:Quasar,Javaflow

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

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

有什么不同?

线程-抢先多任务。(通常)。协程-协作多任务。

线程-通常由OS管理。协程-由用户管理。

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


15
好的答案,但是我建议做一个重要的更正。在科特林的协程以前是在初始预释放预览无堆叠,但实际上在科特林1.1与支持在任何堆栈深度释放悬浮液,就像在类星体,例如。对于那些熟悉Quasar的人来说,很容易看到Quasar修改器throws SuspendExecution和Kotlinsuspend修改器之间的一对一对应关系。当然,实现细节完全不同,但是用户体验却非常相似。
Roman Elizarov

5
也欢迎您在相应的设计文档中查看有关Kotlin协程的实际实现的详细信息。
罗曼·伊丽莎拉夫

4
坦白说,我不知道“协程”一词的含义。我没有看到这个术语的任何正式/技术定义,而且我看到不同的人以完全矛盾的方式使用它。我会避免完全使用术语“堆栈协程”。我可以肯定地说,并且易于验证的是,Kotlin协程离Quasar更近,并且与C#非常不同。不管您对“堆栈式协程”一词的定义如何,将Kotlin协程与C#异步放入同一个容器似乎都不对。
Roman Elizarov

9
我将通过以下方式用多种语言对协程进行分类:C#,JS等具有基于将来/承诺的协程。这些语言中的任何异步计算都必须返回某种类似将来的对象。称它们为无堆叠是不公平的。您可以表达任何深度的异步计算,但是它们在语法和实现方面都是低效的。Kotlin,Quasar等具有基于暂停/继续的协程。它们严格来说功能更强大,因为它们可以与类似未来的对象一起使用,也可以不使用它们,而仅通过使用悬浮函数即可使用。
罗曼·伊利萨洛夫

7
好。这是一篇很好的论文,为协程提供了背景,并为“堆栈式协程”提供了或多或少的精确定义:inf.puc-rio.br/~roberto/docs/MCC15-04.pdf这意味着Kotlin实现了堆栈式协程
Roman Elizarov

70

什么是协程像轻质螺纹?

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

有什么不同?

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

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

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

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

在Kotlin中,协程的调度是通过协程上下文控制的。您可以在kotlinx.coroutines指南中了解有关此内容的更多信息。

在这里,我要启动100000个协程,这段代码后面会发生什么?

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

launch刚刚创造了新的协同程序,而CommonPool调度协程到ForkJoinPool.commonPool()其不使用多线程,因此在本例中多个CPU执行。

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


3
如此粗略地讲,这是否意味着启动常规程序类似于在线程队列由用户控制的线程队列中添加作业?
狮子座

2
是。它可以是单个线程的队列,也可以是线程池的队列。您可以将协程查看为更高级别的原语,从而避免手动将业务逻辑的连续性(重新)提交到队列。
Roman Elizarov '18

所以这不是意味着当我们并行运行多个协程时,如果协程数比队列中的线程数大得多,那不是真正的并行性吗?如果是这样,那么这听起来确实类似于Java Executor,这两者之间有任何关系吗?
Leo

6
这与线程没有什么不同。如果线程数大于物理核心数,则不是真正的并行性。不同之处在于,线程是抢先调度在内核上的,而协程是协作
Roman Elizarov,
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.