協(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")
}
}
}