Coroutine 跟 thread 的關係

Coroutine 通常可以理解成輕量化的 thread,但實際運作還是需要被排程到作業系統層級的 thread,然後再被排程到某個 CPU來執行:

相對於 thread,使用 coroutine 的好處是, coroutine 之間的切換快速,需要耗用的系統資源也比較小。

Coroutine 如何排程?

Kotlin 的 coroutine 會被分派到那個 thread 是由呼叫 coroutine builder 時提供的 CoroutineContext 參數來決定。

透過觀察這個範例的輸出,我們可以了解不同 CoroutineContext 的行為:

class CoroutineContextActivity : ConsoleActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        logThread("Begin onCreate")

        launch(UI) {
            logThread("Begin launch(UI)")
            delay(10_000)
            logThread("End launch(UI)")
        }

        launch(CommonPool) {
            logThread("Begin launch(CommonPool)")
            delay(10_000)
            logThread("End launch(CommonPool)")
        }

        launch(Unconfined) {
            logThread("Begin launch(Unconfined)")
            delay(10_000)
            logThread("End launch(Unconfined)")
        }

        launch(newSingleThreadContext("MyOwnThread")) {
            logThread("Begin launch(newSingleThreadContext)")
            delay(10_000)
            logThread("End launch(newSingleThreadContext)")
        }

        logThread("End onCreate")
    }

    fun logThread(msg: String) {
        println("$msg: ${Thread.currentThread().name}")
    }
}

結果:

1:48:50:866 (   1) Begin onCreate: main
1:48:50:937 (   1) Begin launch(Unconfined): main
1:48:50:938 (1719) Begin launch(CommonPool): ForkJoinPool.commonPool-worker-2
1:48:50:967 (   1) End onCreate: main
1:48:50:968 (1721) Begin launch(newSingleThreadContext): MyOwnThread
1:48:51:015 (   1) Begin launch(UI): main
1:49:00:971 (1721) End launch(newSingleThreadContext): MyOwnThread
1:49:00:973 (1720) End launch(Unconfined): kotlinx.coroutines.DefaultExecutor
1:49:00:980 (1724) End launch(CommonPool): ForkJoinPool.commonPool-worker-1
1:49:01:016 (   1) End launch(UI): main

心得:

  • Unconfined 在 suspend 之前會是由 calling thread 直接執行,suspend 之後則是用 DefaultExecutor thread 來執行。
  • CommonPool 會是由 common thread pool 裡面的 thread 來執行,suspend 前後被分配的 thread 有可能不同。
  • UI 只能在 main thread 上執行,並且因為是依賴 Android Looper 來實現的,coroutine 會在 onCreate 結束後才被執行到。
  • newSingleThreadContext 會由新產生的 thread 來執行,因爲創建新的 thread 需要一點時間,coroutine 是在 onCreate 結束後才被呼叫到。

這樣的設計主要有兩個優點:一來提供彈性讓使用者根據使用情境決定要使用那些 thread,二來提供擴充性讓使用者可以設計不一樣的 thread dispatching strategy。

如果想要掌握更進階的 coroutine 用法,Coroutine context and dispatchers 是很不錯的使用指南。