Kotlin编程第一课--(源码篇)31 图解Channel:如何理解它的CSP通信模型?
今天我们来分析 Channel 的源码。
Kotlin 的 Channel 是一个非常重要的组件,在它出现之前,协程之间很难进行通信,有了它以后,协程之间的通信就轻而易举了。在第 22 讲当中,我们甚至还借助 Channel 实现的 Actor 做到了并发安全。
那么总的来说,Channel 是热的,同时它还是一个线程安全的数据管道。而由于 Channel 具有线程安全的特性,因此,它最常见的用法,就是建立 CSP 通信模型(Communicating Sequential Processes)。
不过你可能会觉得,CSP 太抽象了不好理解,但其实,这个通信模型我们在第 22 讲里就接触过了。当时我们虽然是通过 Actor 来实现的,但却是把它当作 CSP 在用,它们两者的差异其实很小。
关于CSP 的理论,它的精确定义其实比较复杂,不过它的核心理念用一句话就可以概括:不要共享内存来通信;而是要用通信来共享内存(Don’t communicate by sharing memory; share memory by communicating)。
可是,我们为什么可以通过 Channe ...
Kotlin编程第一课--(源码篇)30 CoroutineScope是如何管理协程的?
通过前面课程的学习,我们知道 CoroutineScope 是实现协程结构化并发的关键。使用 CoroutineScope,我们可以批量管理同一个作用域下面所有的协程。那么,今天这节课,我们就来研究一下 CoroutineScope 是如何管理协程的。
CoroutineScope VS 结构化并发在前面的课程中,我们学习过 CoroutineScope 的用法。由于 launch、async 被定义成了 CoroutineScope 的扩展函数,这就意味着:在调用 launch 之前,我们必须先获取 CoroutineScope。
// 代码段1public fun CoroutineScope.launch( context: CoroutineContext = EmptyCoroutineContext, start: CoroutineStart = CoroutineStart.DEFAULT, block: suspend CoroutineScope.() -> Unit): Job {}public fun <T> ...
Kotlin编程第一课--(源码篇)29 Dispatchers是如何工作的?
今天,我们来分析 Kotlin 协程当中的 Dispatchers。
上节课里,我们分析了 launch 的源代码,从中我们知道,Kotlin 的 launch 会调用 startCoroutineCancellable(),接着又会调用 createCoroutineUnintercepted(),最终会调用编译器帮我们生成 SuspendLambda 实现类当中的 create() 方法。这样,协程就创建出来了。不过,协程是创建出来了,可它是如何运行的呢?
另外我们也都知道,协程无法脱离线程运行,Kotlin 当中所有的协程,最终都是运行在线程之上的。那么,协程创建出来以后,它又是如何跟线程产生关联的?这节课,我们将进一步分析 launch 的启动流程,去发掘上节课我们忽略掉的代码分支。
我相信,经过这节课的学习,你会对协程与线程之间的关系有一个更加透彻的认识。
Dispatchers在上节课里我们学习过,launch{}本质上是调用了 startCoroutineCancellable() 当中的 createCoroutineUnintercepted() 方法创建了协程。
/ ...
Kotlin编程第一课--(源码篇)加餐五 深入理解协程基础元素
在上一讲当中,我们深入研究了 Kotlin 挂起函数的原理,实际更多的是在了解协程的“基础层”。而接下来,我们将会开始研究协程启动的原理,探索协程的“中间层”。
在第 26 讲里,我曾提到过,Kotlin 的协程框架其实就是协程基础元素组合出来的框架。如果我们想要弄懂 Kotlin 协程,首先就要将它的“基础层”理解透彻。
所以今天,我还是决定来一次加餐,带你系统深入地认识一下 Kotlin 协程当中的基础元素。等你对协程的基础层有了深入认识以后,下节课研究协程启动原理就会轻松一些了。
协程基础元素通过第 26 讲我们现在已经知道,Kotlin 协程的基础元素大致有这些:Continuation、SafeContinuation、CoroutineContext、CombinedContext、CancellationException、intrinsics。
其中的 CoroutineContext、CancellationException 我都已经介绍过了,另外的 CombinedContext,其实就是 CoroutineContext 的一个实现类,而 SafeConti ...
Kotlin编程第一课--(源码篇)28 launch的背后到底发生了什么?
在前面的课程里,我们一直在研究如何使用 Kotlin 协程,比如,如何启动协程,如何使用挂起函数,如何使用 Channel、Flow 等 API。但到目前为止,我们只知道该怎么用协程,对它内部的运行机制并没有深究。
现在我们都知道,launch、async 可以创建、启动新的协程,但我们只能通过调试参数,通过 log 看到协程。比如我们可以回过头来看下第 13 讲当中的代码:
// 代码段1// 代码中一共启动了两个协程fun main() = runBlocking { println(Thread.currentThread().name) launch { println(Thread.currentThread().name) delay(100L) } Thread.sleep(1000L)}/*输出结果:main @coroutine#1main @coroutine#2*/
现在回过头来看,这段代码无疑是非常简单的,runBlocking{} 启动了第一个协程,launch{} ...
Kotlin编程第一课--(源码篇)27 图解挂起函数:原来你就是个状态机?
今天我们来研究 Kotlin 挂起函数的实现原理。
挂起函数,是整个 Kotlin 协程的核心,它的重要性不言而喻。几乎所有协程里的知识点,都离不开挂起函数。而且也正是因为挂起函数的原因,我们才可以使用协程简化异步任务。
今天这节课,我会从这个 CPS 转换开始说起,带你进一步挖掘它背后的细节。在这个过程中,我们还会接触到 Kotlin 库当中的协程基础元素:Continuation、CoroutineContext 与挂起函数的底层联系。最后,我会带你灵活运用下这些知识点,以此进一步完善我们的 KtHttp,让它可以直接支持挂起函数。
好,接下来,我们就正式开始吧!
CPS 转换背后的细节在第 15 讲当中,我们已经初步介绍过挂起函数的用法了:挂起函数,只是比普通的函数多了 suspend 关键字。有了这个 suspend 关键字以后,Kotlin 编译器就会特殊对待这个函数,将其转换成一个带有 Callback 的函数,这里的 Callback 就是 Continuation 接口。
而这个过程,我们称之为 CPS 转换:
以上的 CPS 转换过程中,函数的类型发生了变化:su ...
Kotlin编程第一课--(源码篇)26 协程源码的地图:如何读源码才不会迷失?
在前面学习协程的时候,我们说过协程是 Kotlin 里最重要、最难学的特性。之所以说协程重要,是因为它有千般万般的好:挂起函数、结构化并发、非阻塞、冷数据流,等等。不过协程也真的太抽象、太难学了。即使我们学完了前面的协程篇,知道了协程的用法,但也仍然远远不够,这种“知其然,不知其所以然”的感觉,总会让我们心里不踏实。
所以,我们必须搞懂 Kotlin 协程的源代码。
可问题是,协程的源码也非常复杂。如果你尝试研究过协程的源代码,那你对此一定深有体会。在 Kotlin 协程 1.6.0 版本中,仅仅是协程跟 JVM 相关的源代码,就有 27789 行。如果算上 JavaScript 平台、Native 平台,以及单元测试相关的代码,Kotlin 协程库当中的源代码有接近 10 万行。面对这么多的源代码,我们根本不可能一行一行去分析。
因此,我们在研究 Kotlin 协程的源代码的时候,要有一定的技巧。这里给你分享我的两个小技巧:
理解 Kotlin 协程的源码结构。Kotlin 协程的源代码分布在多个模块之中,每个模块都会包含特定的协程概念。相应的,它的各个概念也有特定的层级结构,只有 ...
Kotlin编程第一课--(源码篇)25 集合操作符:你也会“看完就忘”吗?
从这节课开始,我们就正式进入源码篇的学习了。当我们学习一门知识的时候,总
是离不开 What、Why 和 How。在前面的基础篇、协程篇当中,我们已经弄清楚了 Kotlin 是什么,以及为什么要用 Kotlin。那么在这个模块里,我们主要是来解决 How 的问题,以此从根源上搞清楚 Kotlin 的底层实现原理。今天这节课,我们先来搞定集合操作符的用法与原理。
对于大部分 Java、C 开发者来说,可能都会对 Kotlin 的集合操作符感到头疼,因为它们实在太多、太乱了。即使通过 Kotlin 官方文档把那些操作符一个个过了一遍,但过一段时间在代码中遇到它们,又会觉得陌生。一看就会,看完就忘!
其实,Kotlin 的集合 API,本质上是一种数据处理的模式。
什么是数据处理模式?可以想象一下:对于 110 的数字来说,我们找出其中的偶数,那么这就是一种过滤的行为。我们计算出 110 的总和,那么这就是一种求和的行为。所以从数据操作的角度来看,Kotlin 的操作符就可以分为几个大类:过滤、转换、分组、分割、求和。
那么接下来,我会根据一个统计学生成绩的案例,来带你分析 Kotlin 的 ...
Kotlin编程第一课--(答疑篇)答疑(一)| Java和Kotlin到底谁好谁坏?
由于咱们课程的设计理念是简单易懂、贴近实际工作,所以我在课程内容的讲述上也会有一些侧重点,进而也会忽略一些细枝末节的知识点。不过,我看到很多同学都在留言区分享了自己的见解,算是对课程内容进行了很好的补充,这里给同学们点个赞,感谢你的仔细思考和认真学习。
另外,我看到不少同学提出的很多问题也都非常有价值,有些问题非常有深度,有些问题非常有实用性,有些问题则非常有代表性,这些问题也值得我们再一起探讨下。因此,这一次,我们来一次集中答疑。
Java 和 Kotlin 到底谁好谁坏?
很多同学看完开篇词以后,可能会留下一种印象,就是貌似 Java 就是坏的,Kotlin 就是好的。但其实在我看来,语言之间是不存在明确的优劣之分的。“XX 是世界上最好的编程语言”这种说法,也是没有任何意义的。
不过,虽然语言之间没有优劣之分,但在特定场景下,还是会有更优选择的。比如说,站在 Android 开发的角度上看,Kotlin 就的确要比 Java 强很多;但如果换一个角度,服务端开发,Kotlin 的优势则并不明显,因为 Spring Boot 之类的框架对 Java 的支持已经足够好了;甚至,如果 ...
Kotlin编程第一课--(协程篇)24 实战:让KtHttp支持Flow
Kotlin编程第一课–(协程篇)24 | 实战:让KtHttp支持Flow又到了熟悉的实战环节,这一次我们接着来改造 KtHttp,让它能够支持协程的 Flow API。
有了前面两次实战的基础,这次我们应该就轻车熟路了。在之前的4.0 版本中,为了让 KtHttp 支持挂起函数,我们有两种思路,一种是改造内部,另一种是扩展外部。同理,为了让 KtHttp 支持 Flow,这次的实战也是这两种思路。
因此,这节课我们仍然会分为两个版本。
5.0 版本,基于 4.0 版本的代码,从 KtHttp 的外部扩展出 Flow 的能力。
6.0 版本,修改 KtHttp 内部,让它支持 Flow API。
其实在实际的工作中,我们往往没有权限修改第三方提供的 SDK,那么这时候,如果想要让 SDK 获得 Flow 的能力,我们就只能借助 Kotlin 的扩展函数,为它扩展出 Flow 的能力。而对于工程内部的代码,我们希望某个功能模块获得 Flow 的能力,就可以直接修改它的源代码,让它直接支持 Flow。
那么在这节课里,我会同时用这两种手段来扩展并改造 KtHttp,为你演示其中的 ...