理解协程,我想纠正99.9%的人对协程的认知!
关于协程最广为流传且极具误导性的定义是:『协程是一种轻量化的线程』,这句话让人感觉貌似表达的很清晰透彻实际严重误导了大众,很多人因此认为协程是一个非常神奇的技术,用了协程就可以不需要线程了。谬矣!
💡 本文章不深入探究不同语言的协程的具体使用方法,因为协程之所以被大众认为是一项很难的技术,并不是因为它的使用,而在于它的概念过于混乱,网络充斥着太多混淆的抽象概念,加之这些混淆的概念被各种博客互抄传播,使它蒙上了一层神秘的面纱。
解锁新技术步骤:始于定义(What),探寻动机(Why),终至实践(How)
📢 协程(coroutine)定义:
对于协程 的定义,很多博客都把它和线程作对比,但是在本质上它与线程 完全不是同一层级的概念, 而与协程在语法定义上相似的概念是:子例程、方法、函数、闭包。所以协程本质上就是一段封闭的代码单元,它们最原始的目的都是实现对代码的复用。
协程与线程的区别与联系
首先强调一点:线程的概念,广义的线程就是指操作系统线程,一个CPU物理线程是真实运行的线程,而操作系统线程是对CPU线程的抽象,语言层的线程又是对操作系统线程的抽象,它们本质上是一一对应关系,拒绝混淆线程概念(比如Java19中提出了虚拟线程,它与线程非同一概念),线程是唯一的并发实体。
在维基百科中,关于协程与线程的关系有以下表述:

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

我个人理解:维基百科中并没有想表达协程是轻量级线程这一含义,而表达的是协程这项技术出现的目的是同线程一样提供一种并发解决方案,但这种方案比线程更轻量化 ,因此,应该换成「协程是一种轻量化的并发解决方案
」,这样才更准确,并且更能表达出协程的本质。
并且维基百科中关于协程与线程的区别表述很明确,协程是语言层的概念,而线程是操作系统层的概念,协程一定是跑在线程之上的。
协程没有所谓官方统一确定的概念,也没有统一的实现标准,因此不同的语言实现的所谓的「协程」可能完全不是一个东西!但他们都叫Coroutine, 上面维基百科中也表达了这一区别:可以使用抢占式调度的线程来实现协程。因此协程在不同语言上的实现我认为一般有两种:
-
协程设计初衷之一是减少线程切换的开销,尤其是在处理大量耗时 I/O操作时。因此这种协程实现的是可以优化程序整体性能的协程,例如 Go语言的Goroutine是基于这种底层逻辑实现的协程。
这种协程模型主要是针对 I/O 密集性任务,因为 I/O 密集型任务的主要耗时点是磁盘读写,网络请求等任务,不会占用大量 CPU 资源。当挂起时,仍然可以在后台进行 I/O 任务,而不需要CPU参与,从而提高了对CPU时间片的利用率。它的底层逻辑是「榨干CPU等待时间」。
-
另外一种定义就是使用抢占式线程来实现的协程,本质上这种协程是一种线程框架,例如 Kotlin 协程,它基于Java线程池来实现的协程,在性能上并不比Java线程更好,但由于使用流式API,使并发编程比Java多线程编程的方便了许多。因此本人认为 轻量化的并发实现方案 这一描述对于Kotlin协程也有点勉强。
以上两种协程的实现,即所谓的真假协程,本文不讨论真假协程的表述是否准确严谨,仅用于区分。
我个人认为区分真假协程的核心在于:协程能否做到在单个操作系统线程内实现非阻塞的IO操作
Kotlin协程之所以被称为假协程,是因为Kotlin协程必须运行在JVM之上,受限于JVM线程的限制,无法在执行IO的过程中不阻塞当前线程。