Kotlin Primer·第七章·协程库(上篇)

7.协程

协程,协作代码段。相对线程而言,协程更适合于用来实现彼此熟悉的程序组件。协程提供了一种可以避免线程阻塞的能力,这是他的核心功能。在 kotlin 中使用协程,需要在 gradle 中引入协程库:

  • //Android 工程使用
  • implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:x.x.x"
  • //Java 工程使用
  • implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:x.x.x'

7.1 协程是什么

协程的概念其实是很早就被提出的。
这里借用知乎作者阿猫的一段回答(有所修改)来为大家讲解协程究竟是怎么来的:

  1. 一开始大家想要同一时间执行多个代码任务,于是就有了并发。从程序员的角度可以看成是多个独立的逻辑流,内部可以是多 CPU 并行,也可以是单 CPU 时间分片。
  2. 但是一并发就有上下文切换的问题,干了一半跑去处理另一件事,我这做了一半的东西怎么保存。进程就是这样抽象出来个一个概念,搭配虚拟内存、进程表之类,用来管理独立的程序运行、切换。
  3. 后来硬件提升了,一电脑上有了好几个 CPU 就可以一人跑一进程,就是所谓的并行
  4. 但是一并行,进程数一高,大部分系统资源就得用于进程切换的状态保存。后来搞出线程的概念,大致意思就是这个地方阻塞了,但我还有其他地方的逻辑流可以计算,不用特别麻烦的切换页表、刷新 TLB,只要把寄存器刷新一遍就行。
  5. 如果你嫌操作系统调度线程有不确定性,不知道什么时候开始什么时候切走,我自己在进程里面手写代码去管理逻辑调度这就是用户态线程
  6. 而用户态线程是不可剥夺的,如果一个用户态线程发生了阻塞,就会造成整个进程阻塞,所以进程需要自己拥有调度线程的能力。而如果用户态线程将控制权交给进程,让进程调度自己,这就是协程

后来我们的内存越来越大,操作系统的调度也越来越智能,就慢慢没人再去花时间去自己实现用户态线程、协程这些东西了。

7.2 为什么又要用协程了

那既然上面说协程已经淘汰在历史的长河中了,为什么现在又跑来这么声势浩大。
这就要从多线程的效率讲起了。
前面我们讲由于操作系统的多线程调度越来越智能,硬件设备也越来越好,这大幅提升了线程的效率,因此正常情况下线程的效率是高于协程的,而且是远高于协程。
那么线程在什么情况下效率是最高的?就是在一直 run 的情况下。但是线程几乎是很难一直 run 的,比如:线程上下文切换、复杂计算阻塞、IO阻塞。
于是又有人想起了协程,这个可以交给代码调度的东西。

7.3 kotlin 的协程怎么用

在 kotlin 上,使用协程你只需要知道两个方法和他们的返回类型,就可以很熟练的用上协程了。分别是:

  • fun launch(): Job
  • fun async(): Deferred

7.3.1 launch方法

从方法名我们就能看出,launch表示启动一个协程。

  • public fun launch(
  • context: CoroutineContext,
  • start: CoroutineStart = CoroutineStart.DEFAULT,
  • block: suspend CoroutineScope.() -> Unit
  • ): Job {
  • }

launch()方法接收三个参数,通常很少用到第二个参数。
第一个参数是一个协程的上下文,CoroutineContext不仅可以用于在协程跳转的时刻传递数据,同时最主要的功能,是用于表明协程运行与恢复时的上下文环境。
通常Android在用的时候都是传一个UI就表示在 UI 线程启动协程,或者传一个CommonPool表示在异步启动协程,还有一个是Unconfined表示不指定,在哪个线程调用就在哪个线程恢复。

  • fun test() {
  • launch(UI) {
  • val isUIThread = Thread.currentThread() == Looper.getMainLooper().thread
  • println("UI::===$isUIThread")
  • }
  • launch(CommonPool) {
  • val isUIThread = Thread.currentThread() == Looper.getMainLooper().thread
  • println("CommonPool::===$isUIThread")
  • }
  • }

例如这段代码就会输出一个

  • UI::===true
  • CommonPool::===false

7.3.2 Job对象

launch()方法会返回一个job对象,job对象常用的方法有三个,叫startjoincancel。分别对应了协程的启动、切换至当前协程、取消。

例如下面是start()方法的使用示例:

  • fun test() {
  • //当启动类型设置成LAZY时,协程不会立即启动,而是手动调用start()后他才会启动。
  • val job = launch(UI, CoroutineStart.LAZY) {
  • println("hello")
  • }
  • job.start()
  • }

join()方法就比较特殊,他是一个suspend方法。suspend 修饰的方法(或闭包)只能调用被suspend修饰过的方法(或闭包)。 方法声明如下:

  • public suspend fun join()
top Created with Sketch.