什么是「非阻塞式掛起」
非阻塞式是相對阻塞式而言的。
編程語言中的很多概念其實都來源于生活,就像脫口秀的段子一樣。
線程阻塞很好理解,現(xiàn)實中的例子就是交通堵塞,它的核心有 3 點:
- 前面有障礙物,你過不去(線程卡了)
- 需要等障礙物清除后才能過去(耗時任務結束)
- 除非你繞道而行(切到別的線程)
從語義上理解「非阻塞式掛起」,講的是「非阻塞式」這個是掛起的一個特點,也就是說,協(xié)程的掛起,就是非阻塞式的,協(xié)程是不講「阻塞式的掛起」的概念的。
我們講「非阻塞式掛起」,其實它有幾個前提:并沒有限定在一個線程里說這件事,因為掛起這件事,本來就是涉及到多線程。
就像視頻里講的,阻塞不阻塞,都是針對單線程講的,一旦切了線程,肯定是非阻塞的,你都跑到別的線程了,之前的線程就自由了,可以繼續(xù)做別的事情了。
所以「非阻塞式掛起」,其實就是在講協(xié)程在掛起的同時切線程這件事情。
為什么要講非阻塞式掛起
既然第三篇說的「非阻塞式掛起」和第二篇的「掛起要切線程」是同一件事情,那還有講的必要嗎?
是有的。因為它在寫法上和單線程的阻塞式是一樣的。
協(xié)程只是在寫法上「看起來阻塞」,其實是「非阻塞」的,因為在協(xié)程里面它做了很多工作,其中有一個就是幫我們切線程。
第二篇講掛起,重點是說切線程先切過去,然后再切回來。
第三篇講非阻塞式,重點是說線程雖然會切,但寫法上和普通的單線程差不多。
讓我們來看看下面的例子:
???
main {
GlobalScope.launch(Dispatchers.Main) {
// ?? 耗時操作
val user = suspendingRequestUser()
updateView(user)
}
private suspend fun suspendingRequestUser() : User = withContext(Dispatchers.IO) {
api.requestUser()
}
}
從上面的例子可以看到,耗時操作和更新 UI 的邏輯像寫單線程一樣放在了一起,只是在外面包了一層協(xié)程。
而正是這個協(xié)程解決了原來我們單線程寫法會卡線程這件事。
阻塞的本質
首先,所有的代碼本質上都是阻塞式的,而只有比較耗時的代碼才會導致人類可感知的等待,比如在主線程上做一個耗時 50 ms 的操作會導致界面卡掉幾幀,這種是我們?nèi)搜勰苡^察出來的,而這就是我們通常意義所說的「阻塞」。
舉個例子,當你開發(fā)的 app 在性能好的手機上很流暢,在性能差的老手機上會卡頓,就是在說同一行代碼執(zhí)行的時間不一樣。
視頻中講了一個網(wǎng)絡 IO 的例子,IO 阻塞更多是反映在「等」這件事情上,它的性能瓶頸是和網(wǎng)絡的數(shù)據(jù)交換,你切多少個線程都沒用,該花的時間一點都少不了。
而這跟協(xié)程半毛錢關系沒有,切線程解決不了的事情,協(xié)程也解決不了。
協(xié)程與線程
協(xié)程我們講了 3 期,Kotlin 協(xié)程和線程是無法脫離開講的。
別的語言我不說,在 Kotlin 里,協(xié)程就是基于線程來實現(xiàn)的一種更上層的工具 API,類似于 Java 自帶的 Executor 系列 API 或者 Android 的 Handler 系列 API。
只不過呢,協(xié)程它不僅提供了方便的 API,在設計思想上是一個基于線程的上層框架,你可以理解為新造了一些概念用來幫助你更好地使用這些 API,僅此而已。
就像 ReactiveX 一樣,為了讓你更好地使用各種操作符 API,新造了 Observable 等概念。
說到這里,Kotlin 協(xié)程的三大疑問:協(xié)程是什么、掛起是什么、掛起的非阻塞式是怎么回事,就已經(jīng)全部講完了。非常簡單:
協(xié)程就是切線程;
掛起就是可以自動切回來的切線程;
掛起的非阻塞式指的是它能用看起來阻塞的代碼寫出非阻塞的操作,就這么簡單。
當然了,這幾句是總結,它們背后的原理你是一定要掌握住的。如果忘了,再去把之前的視頻和文章看一遍就好。
視頻中還糾正了官方文檔里面的一個錯誤,這里就不再重復了,最后想表達一點:
Kotlin 協(xié)程并沒有脫離 Kotlin 或者 JVM 創(chuàng)造新的東西,它只是將多線程的開發(fā)變得更簡單了,可以說是因為 Kotlin 的誕生而順其自然出現(xiàn)的東西,從語法上看它很神奇,但從原理上講,它并不是魔術。