关键词:Kotlin 协程
趁着面试官还没看,赶紧深入了解一下~~
朋友们好呀~
鼠年进入了尾声,想想过去的这一年还是发生了很多事情:终于有了回家的高铁,可刚回家没几天就又匆匆踏上了返京的旅途;写了一本书;做了一次线下的大会分享;做了两次线上的分享;上线了一套课程;买了 * * *;在公司也终于起了一个跨平台(Android Linux)的项目,体验了一把 Kotlin MPP 等等,话说我司今年的阳光有点儿刺眼啊 :)。想了想,这一年也过得挺充实。
不过,也有些地方做的不好,例如书出版之后就没怎么管过了,就感觉这本书不是我写的一样。最近终于得空,为了了解一下读者遇到的问题,就去京东上刷了一下本书的评论,发现其中的内容非常有趣。在对这些评论做出回应的同时,我也希望能够对大家阅读本书提供一些帮助。
本书收到的评论
以下来自京东自营商品的商品评价,用户关键信息已打码。
负面评论
差评一:完全没干货
不正经的回应:我都惊呆了,居然说我的书没干货!你知道吗,以前大家可都是吐槽我写的东西干货太多了呢 ψ(*`ー´)ψ。至于说“一会儿就翻完了”这事儿,200 多页如果只是翻的话,确实很快 (`・ω・´)。
正经的回应:想要了解 Kotlin 协程的底层原理,请参见:本书第 3 ~ 5 章,其中第 3 章重要从底层的标准库 API 讲起,对挂起函数的本质、协程上下文、拦截器等概念做了深入的讲解;第 4 章主要是对第 3 章知识的应用;第 5 章则是对官方协程框架内核的剖析,剖析的方法就是我们一步一步地实现了一套精简版的协程框架 CoroutineLite。
差评二:一上来就讲原理,压根接受不了
不正经的回应:我没有,我不是,别瞎说啊 ┗( ▔, ▔ )┛
正经的回应:实际上本书的开篇并没有讲原理,而是讲概念。我们必须先统一对概念的理解和认识,才能够在后续的深入理解上畅通无阻。因此,第 1 章是对整体异步程序的概念做了剖析,这是整个协程概念的基础;第 2 章剖析协程的概念,光讲概念太抽象,于是我还结合了常见的协程实现做了阐述。
中评一:代码贴得多,理论分析少;内容不深刻,停留在基础阶段
不正经的回应:额,你可能不知道,我的书刚出版的时候,群里的小伙伴们看了之后都在说比某某源码**强太多了,是我听错了么 (;´д`)ゞ
正经的回应:两个问题:
- 代码贴得多不多这个问题,技术书籍难免需要贴代码。到底什么是多,每个人的看法不一样,我只给出统计数据(基于本书底稿,与最终排版结果可能有稍许出入)( ̄︶ ̄)↗:本书纯文字部分约 6000 行;插图共计 67 幅;代码部分约 3350 行,其中 Kotlin 约 3200 行,Java 62 行,JavaScript 41 行,Python 27 行,Lua 25 行,C 11 行。插图排版之后平均约占 10 行的篇幅,由此估计本书文字部分占比约 60%,插图部分占比约 7%,代码部分占比约 33%。
- 内容不深刻,停留在基础阶段。不错,我确实是花了三分之一的篇幅讲基础,也花了三分之一的篇幅讲进阶,还花了三分之一的篇幅讲实战,你不妨看完第一章第一节之后再往后看一看(Θ0Θ●)?
中评二:缺乏系统性介绍;代码格式很??(目测是很乱的意思)
不正经的回应:你是不喜欢 JetBrains Mono 吗?难道你的代码用的是微软雅黑?╮(╯﹏╰)╭
正经的回应:也是两个问题:
- 缺乏系统性介绍这个问题,可能真的不存在。我也做过反思,这本书可能最大的问题是没有迎合多数开发者“速成 Kotlin 协程”的想法,也就是说我可能就是因为讲的太系统了 (๑╹◡╹)ノ""" —— 但饭要一口一口吃嘛,速成的东西又有什么价值呢?
- 至于代码格式,审美这个东西也是因人而异。还是老规矩,我只列出我做过的一些努力:为了防止被动折行的产生,我将缩进调成了 2 个空格;本书终稿我把所有被动折行的部分全部通过手动重排以使代码看起来更加清晰,也就是说你看到的书上应该很少有代码单行字符数超过 80 的情况;另外,本书代码全部采用 JetBrains Mono 字体。如果你觉得这样不好,也可以评论告诉我具体怎么样是好的。
中评三:篇幅太小,一笔带过,算不上深入理解
不正经的回应:啊,我懂了!作为优秀的打工人,我猜你一定喜欢1000多页的大砖头!ヽ(゚∀゚)メ(゚∀゚)ノ
正经的回应:其实吧,对于本书的评价,负面的主要是两种声音:一种是太深了,读不懂;另一种是算不上深入理解。ε=(´ο`*)))唉,我好难。你好歹看一看书的内容再来喷也不迟啊。
正面评论
整体上正面评论更多一些,我就挑了两个列出来:
不正经的回应:我没啥好说的,我也是这么想的(✪ω✪)!
正经的回应:这种时候根本正经不起来好吧!
本书的阅读建议
咳咳,严肃点儿哈。实际上之所以有差评,就是因为有误会。毕竟大家的知识背景有差异,而一本书想要填平这差异显然是不现实的。当然不排除有人恶意评价,但相信这种情况应该占少数。
而我本人呢,在出版这本书之后几乎没有再对它做过任何补充和说明,甚至连协程的文章都没有再写了,想来也有点儿没道理。有朋友说我简直佛系到家了。(〃'▽'〃)
所以接下来我提供一些我对阅读本书的看法,看看能不能帮助大家更好的阅读这本书。
本书的内容来源
要搞清楚怎么阅读本书,先来看看本书的内容到底是怎么来的。
书中的内容是从我这几年的公众号文章、视频课程以及项目实践当中逐渐积累下来的:
- 看过我的“新版Kotlin从入门到精通(https://coding.imooc.com/class/398.html)”视频课程第11-13章大概 10 个小时的协程内容的朋友可能会对本书的第3-5章比较熟悉,我在这部分内容主要系统的给出了协程的概念,以及框架封装的思路和方法。
"新版Kotlin从入门到精通" 协程相关章节
- 看过我在公众号之前发布的将近 20 篇文章的朋友,可能会对第 6 章以及第 7 章的很小的一部分内容感到熟悉。
协程相关历史文章:关注公众号 Kotlin 回复 "协程" 获取
那么我写这本书是不是在重复消费以前的内容呢?
熟悉我的朋友可能会知道,我可从来不做这样的事儿。因为我也有学习的诉求,我也需要在讲给大家听的过程中保证自己也能学到东西。
而这次编纂成册,其实主要完成了以下两项目标:
- 概念的清晰化。 这一点其实也是为了应对协程概念混乱的现状的。大家讨论来讨论去,还是搞不明白究竟什么是协程,好用不容易从理论的角度摸着点儿门道,一看 Kotlin 代码里面既有 startCoroutine ,也有 launch,于是就又蒙了:它们到底哪个启动的才是协程? 为了解决这个问题,我在本书的前几章给协程在不同应用层面的 API 下了个定义,前面的叫简单协程,后面的叫复合协程,方便我们在行文和讨论过程中区分开来;也总结了协程框架封装的套路,一方面希望大家看到官方框架时不要感到害怕,另一方面也能够在实践中令读者有能力自己做一些扩展。
- 知识的体系化。 除了搞不清楚什么是协程之外,大家最爱问题的问题就是协程有什么用,而且很多时候一些已经对协程掌握得还不错的开发者,当你问到他协程什么情况下能解决异步问题,或者为什么有时候协程并不轻量这些问题的时候,一样大概率讲不清楚。原因在哪儿?多数是因为对于某些问题的应用场景不熟悉,既没有搞清楚问题本身,也没有搞清楚问题的解决方案。 为了解决这个问题,我在本书的第 7、8 两章当中从问题本身出发,对比多年的解决方案的演进过程,进而阐述 Kotlin 协程的作用。搞清楚来龙去脉,自然很多问题也就迎刃而解了。
本书的目标读者
本书的目标读者是真正希望能深入理解 Kotlin 协程的开发者。如果你只是想要速成,那本书真的会让你失望。
img
话说到这儿,可能多数开发者阅读本书的想法都是要速成 Kotlin 协程,那我应该照着这个思路去安排书的内容。没错,从商业化的角度,是应该这样,我又何尝不想写一本爆款,好好搏一把名声呢?
但事儿可以这么想,但却不能这么做。我们要尊重客观事实,速成 Kotlin 协程这件事儿,真的不靠谱,因为你可能搞不清楚挂起函数到底是怎么挂起的,你也搞不清楚调度器到底是怎么调度协程的,你甚至还会在协程内部随意访问外部变量,以至于出了问题也根本不知道如何下手去解决。
而这些问题的细节,只有我们试图“深入理解 Kotlin 协程”时,才能彻底明白。
至于只是希望把协程当做切换线程的工具的朋友,你确实没必要阅读本书。非要跟我杠协程只是一个线程框架或者协程只是线程的封装的朋友,建议你把我拉黑。
img
另外,由于 Kotlin 协程算是 Kotlin 的高级特性了,对于 Kotlin 的基础语法要求比较高,这其中对于 Kotlin 的函数基础的要求尤为突出,因此读者需要有扎实的 Kotlin 语言基础。
本书的内容安排
第一章:搞清楚异步程序设计
- 内容难度:※
- 阅读方式:掌握
想要搞清楚协程,必须先搞清楚异步。
之前有人在我的博客下跟我争论什么是异步,提到异步必然切线程的事儿,这恐怕也是令大多数开发者感到困惑的地方。
不切线程怎么实现异步?不理解这一点,说到底,还是对于程序设计了解得太少了。如果跳出我们平常习惯的领域,多看一看其他领域的程序设计,其实很容易想到:写 JavaScript 程序的开发者可能都没怎么接触过线程,但这也不妨碍人家使用异步 API。再说一个大家熟悉的,基于 Android Handler 的 post 方法,需要切线程吗?
本书的开篇就在讲什么是异步,想要搞清楚异步程序设计,先仔细阅读这一章。这一章提到的异步程序设计思路,也正与后面第五章剖析官方协程框架设计思路相对应。
第二章:搞清楚协程的概念
- 内容难度:※※
- 阅读方式:了解
网上关于协程的讨论是非常多的。我们在试图搞清楚 Kotlin 协程是什么的时候也难免会看到有人在讨论 Lua 协程是什么,async/await 是什么,Go routine 又是什么。大家都不一样,怎么都叫协程?这就是第二章讲的内容。
这块儿有人也提出质疑:我只想学 Kotlin 协程,你为什么让我看什么 Lua、JavaScript?
实际上,这一章的内容不需要读者对其他语言有深入的了解,我把它们的例子写出来也只是为了让大家找一下协程的感觉,仅此而已。当然,作为开发者,适当的多了解一些编程语言,对于深入认识自己所专注的领域是有帮助的。
第三、四章:搞清楚 Kotlin 协程的基础
- 内容难度:第三章 ※※※ 第四章 ※※※※
- 阅读方式:第三章的内容需要理解,第四章仅供参考可跳过
Kotlin 协程难就难在它的各种细节都被暴露了出来。相比之下无论是 Go routine、还是 JavaScript 的 async/await,内部实现细节都没有暴露,对于这些语言的协程设计,开发者确实可以实现“速成”。
那这么说 Kotlin 协程的设计很失败啊。
其实不然。
协程的设计实现往往需要运行时的支持,也正是如此,Go routine 不需要暴露它的调度细节,这些工作全部由 Go 运行时来搞定;JavaScript 的 async/await 也不用,因为 JavaScript 的环境绝大多数都是单线程的,运行时也提供了足够多的异步 API 供我们调用,开发者根本不用关心什么线程调度,什么异常处理的细节。
Kotlin 为什么不能在运行时提供此类支持呢?因为多数情况下,Kotlin 没有自己的运行时。Kotlin/JVM 运行在 JVM 上(或者 Android VM 上),Kotlin/JS 运行在 JavaScript 的环境当中(可能是浏览器,也可能是 Node.js 等等),没有自己的运行时的结果就是 Kotlin 协程只能在编译时加入“魔法”。它不能保证运行时能够自动处理调度;也不能保证运行时就只有一个线程;面对 JVM,它还必须能够灵活的切换线程;JVM 上的 API 多数都是阻塞的,它还要能够方便的实现阻塞与非阻塞的转换。所以它能简单吗?它不能。
还有一个原因,Kotlin 要面向多平台来实现自己的协程。当它运行在 JVM 上时,Java 开发者可能用它来做高并发的服务,类似于 Go routine 那样的“轻量级线程”的使用场景更多;Android 开发者可能用它来做异步 UI,类似于 JavaScript 的 async/await 的场景更多。当它运行在 JavaScript 环境中时,还有能与 JavaScript 基于 Promise 的 async/await 无缝衔接。而当它运行在 Native 环境中时,Kotlin 官方现在似乎还没有完全确定最终能够实现成什么样。
没有自己的运行时,还有面对这么多的目标场景,Kotlin 协程的设计能够设计成这样,也实属不易。每次想起这个事儿我都不得不感叹现在身为 Kotlin Leader 的 Roman 在设计协程时面临了怎样的挑战,不过大佬毕竟是大佬:
讲到这儿,我想说的是,一定要踏踏实实地搞清楚第三章的内容,这是整个 Kotlin 协程的基础。第四章是运用第三章的知识在落地实践,我在这一章也会摸索出一套构建协程框架的思路,这个思路将在第五章得到进一步实践。
对了,稍微提一句的是,我在 Kotlin 教学过程中发现多数开发者搞不清楚 Receiver,对于 Kotlin 的函数的认知也比较浅,这会成为 Kotlin 协程学习的严重阻碍。再次强调,想要学好 Kotlin 协程,Kotlin 的基本功要扎实。
第五、六章:搞清楚官方的协程框架
- 内容难度:第五章 ※※※※※ 第六章 ※※※
- 阅读方式:第五章配合源码了解即可,第六章掌握框架的使用方法
官方的协程框架实际上也是分了两个层次的。核心层就是我在第五章手把手带着大家实现的 CoroutineLite(https://github.com/enbandari/CoroutineLite) 所涉及的部分,包括调度器、异常处理、取消响应、作用域等内容,这部分内容官方没有特别多的文档,有些地方的设计也比较反直觉,所以如果搞不清楚其本质,很难在实际应用中做到心中有数。
我也是在对官方框架不断地调试过程中逐渐分析总结,才形成了第五章的内容基础。为了降低对官方框架的理解难度,我从零开始一步一步介绍了 CoroutineLite(https://github.com/enbandari/CoroutineLite) 的设计和实现过程,框架代码整体也就几百行,稍微花点儿时间就能够达到掌握官方框架实现细节的水平,可以算是一条捷径了。
当然,第五章真的是难,但难点本质上并不是协程的设计思路本身,而是对很多 Kotlin 基础语法特别是函数特性的综合运用,以及 对 Java 并发包当中的原子类型的运用,读者需要有扎实的 Kotlin 语言功底和 Java 并发编程基础才能很好的理解这一章。
那么问题来了,这一章算是劝退吗?
显然不是,知识本来就是递进的,基础不牢地动山摇,该补充什么背景知识,那就抓紧时间去补充。我已经知道有不少读者把这一章吃透了,甚至有朋友看过之后直呼过瘾,这说明只要有一定的基础,这一章就不是那么的难。
第六章就比较中规中矩了,是对官方协程框架的应用层做的一些介绍,以用法为主,因此难度相对低一些。稍微提一句,协程框架的应用层(包括 Channel、Flow 等等)还在不断演进,因此本书的部分内容(例如 Flow,本书编写时还在实验阶段)可能与最新版本有些许出入,但这并不会影响你掌握官方的协程框架,因为前面的内容已经足够支撑你通过阅读官方 API 来了解其用法了。
第七、八章:搞清楚协程的使用场景
- 内容难度:※※※
- 阅读方式:Android 开发者重点看第七章,Java 开发者重点看第八章
这部分内容主要就 Android 应用开发和 Java Web 服务应用开发过程当中遇到的异步和并发问题展开介绍。通过对比以往的 API 和协程风格的 API 之间的使用差异,以使读者充分了解协程的应用场景。
实际上,这部分内容也对于协程在什么情况下表现得“轻量”以及怎么解决异步问题结合实际问题给出了答案,是理论联系实际的一部分内容。
第九章:搞清楚协程对多平台的支持情况
- 内容难度:※※※※
- 阅读方式:了解即可
多平台作为 Kotlin 重要特性,Kotlin 协程对多平台的支持自然是对 Kotlin 协程进行系统化剖析时不可或缺的组成部分。但由于多平台特性仍处于快速演进的阶段,因此这部分内容很可能会在不久的将来“过时”。不过这并不影响大家通过阅读本章来理解 Kotlin 协程在多平台支持过程中的努力和方向。
当然,这部分内容主要面向有跨平台需要的开发者,暂时没有多平台应用需要的读者,可以跳过或者简单浏览本章的内容。
本书的一些资源
本书勘误
从书出版的一开始,我一直在维护勘误表,也不能算是对这本书不管不顾吧。(▼ヘ▼#)
这本书是在 2020 年 6 月出版的。截止目前经读者反馈,第一版有共计 9 处问题,还好,问题不大,很容易就能看出来:
《深入理解 Kotlin 协程》勘误表
这里也要感谢为本书勘误的读者。大家看,9 个问题有 7 个是一位 ID 叫 llt 的兄弟提出来的,他现在也是我们下面提到的 QQ 群里面目前为止唯一的管理员。
本书的勘误我一直在我的博客当中关于本书的页面(https://www.bennyhuo.com/project/kotlin-coroutines.html)上有更新。
哦对了,这些问题在 2020 年 10 月印刷的版本里面已经看不到了,所以如果你发现书上的内容居然是对的,也不要感觉惊讶。(^_−)☆
本书源码
本书源码在 GitHub 上完整的给了出来:DiveIntoKotlinCoroutines-Sources(https://github.com/enbandari/DiveIntoKotlinCoroutines-Sources)(没想到已经 100 多个小星星了(~ ̄▽ ̄)~ )
实际上如果你基础不错,自己研究一下 CoroutineLite(https://github.com/enbandari/CoroutineLite) 这个项目一样可以对协程有深入的了解。之前看到有群友说这本书最有价值的部分应该就是 CoroutineLite 这个框架了 —— 是不是最有价值,不同人有不同的看法,但目前还没有看到第二个有类似功能的框架供大家参考。
小结
感谢每一位支持和帮助我的朋友,也希望《深入理解 Kotlin 协程》这本书能够帮助到大家。
最后,祝大家春节快乐~