Day4:Coroutine 的四大特点

在前一篇文章中,我们完成了一个 Coroutine 的程序,并且在最後我们发现了两个特点:

  1. 用 Coroutine 完成的程序码可以让非同步程序码以同步的程序码来撰写。
  2. 我们可以轻松的切换执行绪。

除了上述这两点,我认为 Coroutine 有四个特点:

  1. 编写非同步程序与同步程序一样容易。
  2. Coroutines 可看作是轻量级的执行绪。
  3. 可以轻易地在执行绪中切换。
  4. 轻松取消 Coroutine 的执行。

下面我们就依序来看一下这几个特点吧,

1. 编写非同步程序与同步程序一样容易。

前面提到,我们可以使用 Callback 来处理非同步的运算,但是这样就让程序码变得不容易维护,因为如果 Callback 的层次过多,会发生 Callback hell 的情况。另外,当使用 Callback 时,我们等於是把控制权交给上一个函式,也就是控制权转移,假如上一个函式如果没有妥善呼叫 Callback ,反而会造成误动作。(可以参考上一篇文章的介绍)

将程序码采用 Coroutine 的方式来完成,可以让非同步的程序码的也可以像是同步的程序码一样呼叫,自然也就不会有 Callback hell 以及控制权转移的情况了。

2. Coroutines 可看作是轻量级的执行绪。

「可看作」的意思就是不是完全一样。

官网的介绍如下:

Coroutines can be thought of as light-weight threads, but there is a number of important differences that make their real-life usage very different from threads. - Ref

使用 Coroutine 来解决非同步程序的执行问题,且因为它很轻量,所以我们可以同时建立很多个 Coroutine 也不会造成执行绪的阻塞。

时间测量 (10万个 Coroutine VS 10 万个执行绪)

  • 建立 10万个 Threads
val threadExecuteTime = measureTimeMillis {
        repeat(100_000) {
            thread {
                // Do nothing
            }.start()
        }
    }
    println("Completed, duration: $threadExecuteTime ms")
//Completed, duration: 17187 ms

thread{} 是 Kotlin 中用来建立执行绪的方法。

  • 建立 10万个 Coroutines
val coroutinesExecuteTime = measureTimeMillis {
        runBlocking {
            repeat(100_000) { // launch a lot of coroutines
                launch {
									//Do nothing
                }
            }
        }
    }
println("Completed, duration: $coroutinesExecuteTime ms")

//Completed, duration: 493 ms

launch{} 用来建立新的 Coroutine 以及 Scope。

建立同样数量的 Coroutines 以及 Threads,Coroutine 的执行时间比 Thread 的执行时间要来得少。(493ms vs 17187ms)

为什麽 Coroutine 可以看作是轻量的执行绪呢?

Application-Process-Thread-Coroutine

预设的情况之下,每一个应用程序 (Application) 有一个程序 (Process) 以及一个执行绪 (Thread),称之为主执行绪。程序会占用系统的一块记忆区块,所有该应用程序的程序码以及相关资源都会与其他应用程序的隔离开来,只有自己应用程序才能使用自己记忆区块的内容。执行绪在程序的底下根据分配到的 CPU 时间来执行,如果只有一个执行绪,当有一个耗时工作要处理时,就会一直把取得的 CPU 时间拿去运算,造成画面卡住。所以我们会使用主执行绪以外的执行绪来进行耗时运算。

从上方的图我们可以发现,每一个执行绪都可以拥有多个 Coroutine ,也就是说 Coroutine 把执行绪分为更小单位。所以 Coroutine 可以称得上是轻量级的执行绪。

Coroutine 虽然是在执行绪中,但是它不只是单位更小的执行绪,它与执行绪最大的差异就是执行绪是采取抢占式多工(Preemption multitasking),而 Coroutine 采用的是协作式多工 (Cooperative multitasking),这两种的差异,我们下一篇文章再来讨论。

3. 可以轻易地在执行绪中切换。

由上一点我们知道, 每一个执行绪底下都会有多个 Coroutine,当我们需要切换不同的执行绪来执行时, Coroutine 提供了调度器 (Dispatchers) 让我们能够切换到不同的执行绪上。

注意到,这边是采用调度器来进行切换执行绪的动作,也就是说切换不同的执行绪的动作,这个部分是由 Coroutine 自行处理,我们不需要管理执行绪的建立与停止。

在 Android 中,Coroutine 提供了三个调度器

  1. Dispatchers.Main
  2. Dispatchers.IO
  3. Dispatchers.Default

我们可以根据我们的需求切换到适当的调度器中。

4. 轻松取消 Coroutine 的执行

  • 取消执行绪

使用执行绪时,如果需要把执行绪停止,我们通常会透过一个 flag 来把执行绪停止,如下:

class MyThread(var isRunning: Boolean) {
    lateinit var thread: Thread

    fun run() {
        thread = thread {
            var i = 0
            while (isRunning) {
                println(".")
                Thread.sleep(100)
                i++
                if (i == 10) {
                    stop()
                }
            }
        }
    }

    fun stop() {
        isRunning = false
    }
}

fun main() {
    val myThread = MyThread(true)
    myThread.run()
}

我们使用了 isRunning 这个 flag 来让执行绪可以持续执行,当 isRunning=false 时, while(isRunning){} 里面的内容也不会继续执行,所以当执行绪里面的任务结束时,执行绪也会自动结束。

thread 的 stop() 已经被弃用,我们不能够直接呼叫 stop() 停止执行绪,因为这样会造成 memory-leak。

  • Coroutine

launch 的回传值是一个 Job ,我们可以使用 Job 所提供的 cancel() 来让该 launch 所建立的 Coroutine 给停止下来,如下:

class MyCoroutine {
    lateinit var job: Job
    suspend fun run() = coroutineScope {
        job = launch {
            repeat(100) { i ->
                println("job: wait $i ...")
                delay(500L)
                if (i == 10) {
                    job.cancel()
                }
            }
        }
    }
}

小结

到这边为止,我们知道 Coroutine 的四大特点,是用来解决非同步的程序呼叫流程不易控制、减少占用执行绪的资源、能够有能力在不同的执行绪上做切换以及能够任意的取消 Coroutine。

最重要的是,它是 Kotlin 的亲儿子,如果你是 Kotlin 的开发者,那麽在非同步的解决方案上,不仿考虑 Coroutine 吧。

心智图

Coroutine 四大特点

参考资料

Processes and threads

程序(进程)、执行绪(线程)、协程,傻傻分得清楚!


<<:  D8 第四周 (回忆篇)

>>:  [Day8] IoT Maker之Coding知识科普 - (缩排&条件逻辑判断)

【Day1】下载VsCode来开启我们的前端测试不归路吧(╬•᷅д•᷄╬)

嗨各位看官们,对~又是我! 这是第二次参加铁人赛,除了要检视自己是不是有进步以外, 也想把这一年来有...

k8s prometheus 监控多个MySql

【YC的寻路青春】 这边我们用的是接线生prometheus-operator的版本 namespa...

Day35:HTML(32)响应式网站(2)

响应文字大小 可以使用“ vw”单位设置文本大小,即“视口宽度”。 这样,文本大小将遵循浏览器窗口的...

学习Python纪录Day2 - Python基本介绍

Python介绍: Python具备两种特性,(1)直译式语言 (2)语法友善 因为Python是一...

VPC (一)

VPC介绍 介绍完关於GCP使用这权限设置,再来需要了解的是GCP中的网路层,在网路部分可以说是极其...