Kotlin協(xié)程筆記

協(xié)程與線程關(guān)系

  • 協(xié)程是輕量級線程、比線程耗費(fèi)資源少 這話雖然是官方說的,但我覺得有點(diǎn)誤導(dǎo)的作用,協(xié)程是語言層面的東西,線程是系統(tǒng)層面的東西,兩者沒有可比性。
    協(xié)程就是一段代碼塊,既然是代碼那就離不開CPU的執(zhí)行,而CPU調(diào)度的基本單位是線程。
  • 協(xié)程是線程框架
    協(xié)程解決了異步編程時過多回調(diào)的問題,既然是異步編程,那勢必涉及到不同的線程。Kotlin 協(xié)程內(nèi)部自己維護(hù)了線程池,與Java 線程池相比有些優(yōu)化的地方。在使用協(xié)程過程中,無需關(guān)注線程的切換細(xì)節(jié),只需指定想要執(zhí)行的線程即可,從對線程的封裝這方面來說這說話也沒問題。
  • 協(xié)程效率高于線程 與第一點(diǎn)類似,協(xié)程在運(yùn)行方面的高效率其實(shí)換成回調(diào)方式也是能夠達(dá)成同樣的效果,實(shí)際上協(xié)程內(nèi)部也是通過回調(diào)實(shí)現(xiàn)的,只是在編譯階段封裝了回調(diào)的細(xì)節(jié)而已。因此,協(xié)程與線程沒有可比性。

suspend修飾的掛起于協(xié)程的意義

  • 當(dāng)函數(shù)被suspend 修飾時,表明協(xié)程執(zhí)行到此可能會被掛起,若是被掛起那么意味著協(xié)程將無法再繼續(xù)往下執(zhí)行,直到條件滿足恢復(fù)了協(xié)程的運(yùn)行。
fun main(array: Array<String>) {
    GlobalScope.launch {
        println("before suspend")//①
        testSuspend()//掛起函數(shù)②
        println("after suspend")//③
    }
}

執(zhí)行到②時,協(xié)程被掛起,將不會執(zhí)行③,直到協(xié)程被恢復(fù)后才會執(zhí)行③。
  • 如果將suspend 修飾的函數(shù)類型看做一個整體的話:
suspend () -> T

無參,返回值為泛型。
Kotlin 里定義了一些擴(kuò)展函數(shù),可用來開啟協(xié)程。
  • suspend 修飾的函數(shù)類型,當(dāng)調(diào)用者實(shí)現(xiàn)其函數(shù)體時,傳入的實(shí)參將會繼承自SuspendLambda

如何開啟一個原始的協(xié)程

launch/async/runBlocking 如何開啟協(xié)程

縱觀這幾種主流的開啟協(xié)程方式,它們最終都會調(diào)用到:

#CoroutineStart.kt
    public operator fun <R, T> invoke(block: suspend R.() -> T, receiver: R, completion: Continuation<T>): Unit =
        when (this) {
            DEFAULT -> block.startCoroutineCancellable(receiver, completion)
            ATOMIC -> block.startCoroutine(receiver, completion)
            UNDISPATCHED -> block.startCoroutineUndispatched(receiver, completion)
            LAZY -> Unit // will start lazily
        }

無論走哪個分支,都是調(diào)用block的函數(shù),而block 就是我們之前說的被suspend 修飾的函數(shù)。

以DEFAULT 為例startCoroutineCancellable接下來會調(diào)用到IntrinsicsJvm.kt里的:

#IntrinsicsJvm.kt
public actual fun <R, T> (suspend R.() -> T).createCoroutineUnintercepted(
    receiver: R,
    completion: Continuation<T>
)

該函數(shù)帶了倆參數(shù),其中的receiver 為接收者,而completion 為協(xié)程結(jié)束后調(diào)用的回調(diào)。

為了簡單,我們可以省略掉receiver。

剛好IntrinsicsJvm.kt 里還有另一個函數(shù):

#IntrinsicsJvm.kt
public actual fun <T> (suspend () -> T).createCoroutineUnintercepted(
    completion: Continuation<T>
): Continuation<Unit> 

createCoroutineUnintercepted 為 (suspend () -> T) 類型的擴(kuò)展函數(shù),因此只要我們的變量為 (suspend () -> T)類型就可以調(diào)用createCoroutineUnintercepted(xx)函數(shù)。

查找該函數(shù)的使用之處,發(fā)現(xiàn)Continuation.kt 文件里不少擴(kuò)展函數(shù)都調(diào)用了它。

如:

#Continuation.kt
//創(chuàng)建協(xié)程的函數(shù)
public fun <T> (suspend () -> T).createCoroutine(
    completion: Continuation<T>
): Continuation<Unit> =
    SafeContinuation(createCoroutineUnintercepted(completion).intercepted(), COROUTINE_SUSPENDED)

其中Continuation 為接口:

#Continuation.kt
interface Continuation<in T> {
    //協(xié)程上下文
    public val context: CoroutineContext
    //恢復(fù)協(xié)程
    public fun resumeWith(result: Result<T>)
}

Continuation 接口很重要,協(xié)程里大部分的類都實(shí)現(xiàn)了該接口,通常直譯過來為:"續(xù)體"。

創(chuàng)建完成后,還需要開啟協(xié)程函數(shù):

#Continuation.kt
//啟動協(xié)程的函數(shù)
public inline fun <T> Continuation<T>.resume(value: T): Unit =
    resumeWith(Result.success(value))

Kotlin線程池與Java 線程池比對

  • Java線程池原理:
    • 核心線程+隊(duì)列+非核心線程。
    • 首先使用核心線程執(zhí)行任務(wù),若是核心線程個數(shù)已滿,則將任務(wù)加入到隊(duì)列里,核心線程從隊(duì)列里取出任務(wù)執(zhí)行,若是隊(duì)列已滿,則再開啟非核心線程執(zhí)行任務(wù)。
  • 協(xié)程線程池原理:
    • 全局隊(duì)列(阻塞+非阻塞)+ 本地隊(duì)列。
    • IO 任務(wù)分發(fā)還有個緩存隊(duì)列。
    • 線程從隊(duì)列里尋找任務(wù)(包括偷)并執(zhí)行,若是使用IO 分發(fā)器,則超出限制的任務(wù)將會放到緩存隊(duì)列里。
  • 兩者區(qū)別:
    • Java 線程池開放API,比較靈活,調(diào)用者可以根據(jù)不同的需求組合不同形式的線程池,沒有區(qū)分任務(wù)的特點(diǎn)(阻塞/非阻塞)。
    • 協(xié)程線程池專供協(xié)程使用,區(qū)分任務(wù)特點(diǎn),進(jìn)而進(jìn)行更加合理的調(diào)度。

全局捕獲異常

與線程類似,協(xié)程也可以全局捕獲異常。

    //創(chuàng)建處理異常對象
    val exceptionHandler = CoroutineExceptionHandler { _, exception ->
        println("handle exception:$exception")
    }
    fun testException3() {
        runBlocking {
            //聲明協(xié)程作用域
            var scope = CoroutineScope(Job() + exceptionHandler)
            var job1 = scope.launch(Dispatchers.IO) {
                println("job1 start")
                //異常
                1 / 0
                println("job1 end")
            }
        }
    }
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容