【码上开学】到底什么是「非阻塞式」挂起?协程真的更轻量级吗?

6,779 阅读8分钟

本期作者:

视频:扔物线(朱凯)

文章:Lewis(姚文钦)

大家好,我是扔物线朱凯。今天又是协程,不过今天有一些不一样:

  1. 今天是协程的最后一期(起码近期内最后一期);
  2. 今天的视频最后,我 dui 了 Kotlin 官方。好刺激啊。

这期主要是解释一下挂起的「非阻塞式」是怎么回事,以及对一些最常见的误导性言论进行一下日常打脸——当然了目的当然不是打谁的脸,我没那闲工夫,主要是给大家开个雾。

更多的话就不说了,来看视频吧:

惯例,我不知道怎么在掘金发视频,所以大家可以点 这里 去哔哩哔哩,或者点击 这里 去 YouTube 看。

以下内容来自文章作者 Lewis

大家好,协程的素质三连最后一连终于来了,挖坑容易填坑难。

这段时间真的太忙了,不过我们即刻 Android 团队也对做好「码上开学」的意义又有了新的认识。

接触过大公司的朋友都知道,很多公司会把一些技术的分享列为公司机密,仅限内部分享,你只有加入了公司之后才能看,并且还不能传播出来,而这些被列为机密的东西通常有以下特点:

  • 分享完后鲜有更新,很多知识点已经过时
  • 不涉及具体的业务机密,仅仅是不想让公司外的人员学到
  • 无法进行技术交流,也形不成好的技术社区

希望上面说的没有戳中你所在的公司,这里没有任何 diss 的意思,所以我们要做好「码上开学」,也希望更多有相同志向的业内同行一起参与进来,通过码上开学,我们想表达这样的技术理念:

  • 文章的价值不是只有技术深度这一个维度,深入浅出,找到本质才是重点
  • 文章不是炫技、引战的地方,是大家平等交流,互助成长的地方
  • 技术社区和 Android 一样是生态,需要大家一同维护,而不是各人自扫门前雪,休管他人瓦上霜

在协程系列的前两篇文章中,我们介绍了:

  • 协程就是个线程框架
  • 协程的挂起本质就是线程切出去再切回来

因为时间比较久远了,忘了也很正常,不过今天我们要说的这个「非阻塞式」的概念,即使你没有协程的基础,也可以看懂。

什么是「非阻塞式挂起」

非阻塞式是相对阻塞式而言的。

编程语言中的很多概念其实都来源于生活,就像脱口秀的段子一样。

线程阻塞很好理解,现实中的例子就是交通堵塞,它的核心有 3 点:

  • 前面有障碍物,你过不去(线程卡了)
  • 需要等障碍物清除后才能过去(耗时任务结束)
  • 除非你绕道而行(切到别的线程)

从语义上理解「非阻塞式挂起」,讲的是「非阻塞式」这个是挂起的一个特点,也就是说,协程的挂起,就是非阻塞式的,协程是不讲「阻塞式的挂起」的概念的。

我们讲「非阻塞式挂起」,其实它有几个前提:并没有限定在一个线程里说这件事,因为挂起这件事,本来就是涉及到多线程。

就像视频里讲的,阻塞不阻塞,都是针对单线程讲的,一旦切了线程,肯定是非阻塞的,你都跑到别的线程了,之前的线程就自由了,可以继续做别的事情了。

所以「非阻塞式挂起」,其实就是在讲协程在挂起的同时切线程这件事情。

为什么要讲非阻塞式挂起

既然第三篇说的「非阻塞式挂起」和第二篇的「挂起要切线程」是同一件事情,那还有讲的必要吗?

是有的。因为它在写法上和单线程的阻塞式是一样的。

协程只是在写法上「看起来阻塞」,其实是「非阻塞」的,因为在协程里面它做了很多工作,其中有一个就是帮我们切线程。

第二篇讲挂起,重点是说切线程先切过去,然后再切回来。

第三篇讲非阻塞式,重点是说线程虽然会切,但写法上和普通的单线程差不多。

让我们来看看下面的例子:

🏝️
main {
    GlobalScope.launch(Dispatchers.Main) {
        // 👇 耗时操作
        val user = suspendingRequestUser()
        updateView(user)
    }
    
    private suspend fun suspendingRequestUser() : User = withContext(Dispatchers.IO) {
        api.requestUser()
    }
}

从上面的例子可以看到,耗时操作和更新 UI 的逻辑像写单线程一样放在了一起,只是在外面包了一层协程。

而正是这个协程解决了原来我们单线程写法会卡线程这件事。

阻塞的本质

首先,所有的代码本质上都是阻塞式的,而只有比较耗时的代码才会导致人类可感知的等待,比如在主线程上做一个耗时 50 ms 的操作会导致界面卡掉几帧,这种是我们人眼能观察出来的,而这就是我们通常意义所说的「阻塞」。

举个例子,当你开发的 app 在性能好的手机上很流畅,在性能差的老手机上会卡顿,就是在说同一行代码执行的时间不一样。

视频中讲了一个网络 IO 的例子,IO 阻塞更多是反映在「等」这件事情上,它的性能瓶颈是和网络的数据交换,你切多少个线程都没用,该花的时间一点都少不了。

而这跟协程半毛钱关系没有,切线程解决不了的事情,协程也解决不了。

协程与线程

协程我们讲了 3 期,Kotlin 协程和线程是无法脱离开讲的。

别的语言我不说,在 Kotlin 里,协程就是基于线程来实现的一种更上层的工具 API,类似于 Java 自带的 Executor 系列 API 或者 Android 的 Handler 系列 API。

只不过呢,协程它不仅提供了方便的 API,在设计思想上是一个基于线程的上层框架,你可以理解为新造了一些概念用来帮助你更好地使用这些 API,仅此而已。

就像 ReactiveX 一样,为了让你更好地使用各种操作符 API,新造了 Observable 等概念。

说到这里,Kotlin 协程的三大疑问:协程是什么、挂起是什么、挂起的非阻塞式是怎么回事,就已经全部讲完了。非常简单:

  • 协程就是切线程;
  • 挂起就是可以自动切回来的切线程;
  • 挂起的非阻塞式指的是它能用看起来阻塞的代码写出非阻塞的操作,就这么简单。

当然了,这几句是总结,它们背后的原理你是一定要掌握住的。如果忘了,再去把之前的视频和文章看一遍就好。

视频中还纠正了官方文档里面的一个错误,这里就不再重复了,最后想表达一点:

Kotlin 协程并没有脱离 Kotlin 或者 JVM 创造新的东西,它只是将多线程的开发变得更简单了,可以说是因为 Kotlin 的诞生而顺其自然出现的东西,从语法上看它很神奇,但从原理上讲,它并不是魔术。

希望通过协程系列的讲解能帮助读者上手 Kotlin 协程,不再觉得害怕不敢上手,欢迎继续关注「码上开学」的后续文章,期待和你共同进步。

练习题

使用协程实现一个网络请求:

  • 等待时显示 Loading;
  • 请求成功或者出错让 Loading 消失;
  • 请求失败需要提示用户请求失败了;
  • 让你的协程写法上看上去像单线程。

作者介绍

视频作者

扔物线(朱凯)
  • 码上开学创始人、项目管理人、内容模块规划者和视频内容作者。
  • Android GDE( Google 认证 Android 开发专家),前 Flipboard Android 工程师。
  • GitHub 全球 Java 排名第 92 位,在 GitHub 上有 6.6k followers 和 9.9k stars。
  • 个人的 Android 开源库 MaterialEditText 被全球多个项目引用,其中包括在全球拥有 5 亿用户的新闻阅读软件 Flipboard 。
  • 曾多次在 Google Developer Group Beijing 线下分享会中担任 Android 部分的讲师。
  • 个人技术文章《给 Android 开发者的 RxJava 详解》发布后,在国内多个公司和团队内部被转发分享和作为团队技术会议的主要资料来源,以及逆向传播到了美国一些如 Google 、 Uber 等公司的部分华人团队。
  • 创办的 Android 高级进阶教学网站 HenCoder 在全球华人 Android 开发社区享有相当的影响力。
  • 之后创办 Android 高级开发教学课程 HenCoder Plus ,学员遍布全球,有来自阿里、头条、华为、腾讯等知名一线互联网公司,也有来自中国台湾、日本、美国等地区的资深软件工程师。

文章作者

Lewis(姚文钦)

Lewis(姚文钦),即刻 Android 工程师。2017年加入即刻,参与即刻 4.0 到 6.0 版本的架构设计和与产品迭代。多年 Android 开发经验,现在负责 Android 端多媒体中台基础建设。