协程? Kotlin协程? 理解协程本质,我想纠正99.9%的人对协程的认知!

1,627 阅读6分钟

理解协程,我想纠正99.9%的人对协程的认知!

关于协程最广为流传且极具误导性的定义是:『协程是一种轻量化的线程』,这句话让人感觉貌似表达的很清晰透彻实际严重误导了大众,很多人因此认为协程是一个非常神奇的技术,用了协程就可以不需要线程了。谬矣!

💡 本文章不深入探究不同语言的协程的具体使用方法,因为协程之所以被大众认为是一项很难的技术,并不是因为它的使用,而在于它的概念过于混乱,网络充斥着太多混淆的抽象概念,加之这些混淆的概念被各种博客互抄传播,使它蒙上了一层神秘的面纱。

解锁新技术步骤:始于定义(What),探寻动机(Why),终至实践(How)

📢 协程(coroutine)定义:

对于协程的定义,很多博客都把它和线程作对比,但是在本质上它与线程完全不是同一层级的概念, 而与协程在语法定义上相似的概念是:子例程、方法、函数、闭包。所以协程本质上就是一段封闭的代码单元,它们最原始的目的都是实现对代码的复用。但是协程的特殊点在于它是一段可以挂起和恢复的代码,因此也有人把协程叫做「可以暂停的函数」,不管哪种语言实现的协程,挂起和恢复都是协程最核心的定义。由于这个功能和线程很像,因此它经常拿来和线程进行对比。

协程与线程的区别与联系

首先强调一点:线程的概念,广义的线程就是指操作系统线程,一个CPU物理线程是真实运行的线程,而操作系统线程是对CPU线程的抽象,语言层的线程又是对操作系统线程的抽象,它们本质上是一一对应关系,拒绝混淆线程概念(比如Java19中提出了虚拟线程,它与线程非同一概念),操作系统线程唯一的并发实体

在维基百科中,关于协程与线程的关系有以下表述:

image.png

我不确定这是否是「协程是轻量级线程」这一说法的来源,但在英文版的维基百科 **Coroutine ** 的词条的解释中并没有「轻量级线程」这一表述,以下是英文版(请自行翻译):

image.png

我个人理解:维基百科中并没有想表达协程是轻量级线程这一含义,而表达的是协程这项技术出现的目的是同线程一样提供一种并发解决方案,但这种方案比线程更轻量化,因此,应该换成「协程是一种轻量化的并发解决方案」,这样才更准确,并且更能表达出协程的本质。

并且维基百科中关于协程与线程的区别表述很明确,协程是语言层的概念,而线程是操作系统层的概念,协程一定是跑在线程之上的。

协程没有所谓官方统一确定的概念,也没有统一的实现标准,因此不同的语言实现的所谓的「协程」可能完全不是一个东西!但他们都叫Coroutine, 上面维基百科中也表达了这一区别:可以使用抢占式调度的线程来实现协程。因此协程在不同语言上的实现我认为一般有两种:

  1. 协程设计初衷之一是减少线程切换的开销,尤其是在处理大量耗时 I/O操作时。因此这种协程实现的是可以优化程序整体性能的协程,例如 Go语言的Goroutine是基于这种底层逻辑实现的协程。

    这种协程模型主要是针对 I/O 密集性任务,因为 I/O 密集型任务的主要耗时点是磁盘读写,网络请求等任务,不会占用大量 CPU 资源。当挂起时,仍然可以在后台进行 I/O 任务,而不需要CPU参与,从而提高了对CPU时间片的利用率。它的底层逻辑是「榨干CPU等待时间」。

  2. 另外一种定义就是使用抢占式线程来实现的协程,本质上这种协程是一种线程框架,例如 Kotlin 协程,它基于Java线程池来实现的协程,在性能上并不比Java线程更好,但由于使用流式API,使并发编程比Java多线程编程的方便了许多。因此本人认为 轻量化的并发实现方案 这一描述对于Kotlin协程也有点勉强。

以上两种协程的实现,即所谓的真假协程,本文不讨论真假协程的表述是否准确严谨,仅用于区分。

我认为区分真假协程的核心在于:协程能否做到在单个操作系统线程内实现非阻塞的IO操作

此处非阻塞IO仅指网络IO,因为大部分情况下文件IO都是需要阻塞线程的,除非系统内核有特别支持

Kotlin For JVM的协程与Goroutine核心不同点是,Kotlin是一门多平台语言,它在JVM上实现的协程只能限于编译时,无法左右运行时JVM对线程的调度,而GO协程有自己的运行时调度器。也就是说GO可以在底层维护少量长期运行的OS线程,这些线程不会频繁的创建/销毁,GO Runtime可以自己决定如何分配协程到这些线程上,而分配动作不涉及OS线程的切换开销。Kotlin则不一样,开发者在创建协程时,它就要主动使用JVM的线程池去创建和管理线程,来承载协程内的任务,JVM底层没有长期活动的线程专门用来支撑协程的调度。我认为这是两者在处理高并发任务性能差异的核心点。

PS:

  1. 许多技术博客对于不同语言对协程实现的区分都是基于有栈协程和无栈协程讨论的,这种区分方式主要侧重于协程实现在内存管理上的区别,也是影响协程性能的一个重要方面,但本文是从协程概念提出的初衷进行讨论的,两者是在不同维度对协程实现的讨论,请注意区分