iT邦幫忙

2021 iThome 鐵人賽

DAY 4
0
Software Development

Coroutine 停看聽系列 第 4

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

程序(進程)、執行緒(線程)、協程,傻傻分得清楚!


上一篇
Day3:第一個 Coroutine 程式
下一篇
Day5:深入認識 Coroutine
系列文
Coroutine 停看聽30

尚未有邦友留言

立即登入留言