Kotlin 協(xié)程
本文只是淺析 Kotlin 協(xié)程在各平臺(tái)的實(shí)現(xiàn), 以及跨平臺(tái)兼容方案套利。 如果要看Kotlin協(xié)程api的用法,請(qǐng)移步我的另一篇文章
言歸正傳, 我們聊聊"協(xié)程".
如果有講的不好的地方, 歡迎在下方評(píng)論
協(xié)程介紹
首先, 什么是協(xié)程?
有人說(shuō)協(xié)程擁有自己的寄存器上下文和棧的輕量級(jí)執(zhí)行單元峡碉, 可以在控制執(zhí)行上下文的切換。
熟悉Javascript生成器的人可能會(huì)說(shuō)颂碘,協(xié)程就是回調(diào)的語(yǔ)法糖异赫,本質(zhì)?根本不關(guān)方法棧上下文切換啥事。
到底誰(shuí)說(shuō)的是對(duì)的头岔?
別著急塔拳, 看看維基百科是怎么說(shuō)的:
我們先看看下面這段描述:
Coroutines are computer-program components that generalize subroutines for non-preemptive multitasking, by allowing multiple entry points for suspending and resuming execution at certain locations
協(xié)程就是可以生成非搶占式子程序的計(jì)算機(jī)程序, 可以允許程序有多個(gè)特定的地方可以掛起或者恢復(fù)執(zhí)行峡竣。
Kotlin 協(xié)程在各平臺(tái)編譯成什么靠抑?
熟悉Javascript生成器的人可能會(huì)說(shuō), 協(xié)程就是回調(diào)的語(yǔ)法糖适掰, 本質(zhì)?根本不關(guān)方法棧上下文切換啥事颂碧。
在Kotlin 協(xié)程之前, 我們講講關(guān)于Js的故事类浪。
大家都知道Js有諸多版本载城, 我們經(jīng)常用著Es6、7费就、8的語(yǔ)法, 使用Babel將它們編譯成低版本诉瓦, 再在瀏覽器或者低版本node執(zhí)行。 這里我們單純對(duì)Node.js進(jìn)行討論力细。
低版本的Node有協(xié)程嗎睬澡? 你可以說(shuō)沒(méi)有。實(shí)現(xiàn) coroutine 的方式有很多眠蚂,比如 ES6 的 generator煞聪,ES8(
你沒(méi)有看錯(cuò), 不是ES7
) 的 async/await。在低版本的Node中逝慧, 自然是沒(méi)有協(xié)程的昔脯, 高級(jí)的生成器函數(shù)將會(huì)編譯成普通的Js回調(diào)、狀態(tài)機(jī)代碼(我們后面會(huì)講到
)笛臣, 但是在Node.js 8之后的版本栅干, node.js原生支持了協(xié)程, 實(shí)現(xiàn)了寄存器上下文和棧的切換捐祠。
所以, 我們是不是可以說(shuō)一段高版本Js代碼如果經(jīng)過(guò)編譯成低版本代碼后桑李, 就不在擁有協(xié)程了呢踱蛀?
這么說(shuō)是不公平的, 如果這么說(shuō)的話窿给, 那本文就該換標(biāo)題了, Kotlin就沒(méi)有協(xié)程了
上層不應(yīng)該關(guān)心下層的具體實(shí)現(xiàn)率拒,而只應(yīng)該關(guān)心下層提供的接口
大家都知道崩泡, Jvm也是沒(méi)有原生協(xié)程的,大部分平臺(tái)猬膨, 包括Kotlin-js編譯后的es5 js代碼角撞, 也并沒(méi)有關(guān)于協(xié)程的原生實(shí)現(xiàn), (或者說(shuō)沒(méi)有用到)。 那么把協(xié)程看做重要特性的Kotlin勃痴, 編譯后是怎么樣的呢谒所?
Talk is cheap, show me your code.
讓我們看看下面這段(js 和 kotlin 混寫的)偽代碼
suspend function postItems(item) {
let tk = getToken(item)
let post = doPost(tk)
return post
}
interface StateMachine {
status: number,
item: Any,
continuation: Continuation
}
function postI(item, sm: Continuation) {
if (!sm instanceof StateMachine) {
sm = {
status: 0,
item: null,
continuation: sm
}
}
switch sm.status:
case 1:
sm.status += 1;
getToken(item, (tk) => {
sm.item = tk;
postI(null, sm)
})
case 2:
sm.status += 1;
doPost(sm.item, (post) => {
sm.item = post;
postI(null, sm)
})
case 3:
sm.continuation.resume(sm.item)
return
}
簡(jiǎn)析:
postItems 是一個(gè)kotlin的suspend方法, 他先通過(guò)http請(qǐng)求拿到token沛申, 再通過(guò)token發(fā)起一個(gè)http post 請(qǐng)求劣领, 將返回的對(duì)象 返回給調(diào)用者。
對(duì)應(yīng)編譯后的實(shí)現(xiàn)就成了postI,一個(gè)普通的方法,接受一個(gè)Continuation類型對(duì)象的方法铁材。而這個(gè) Continuation,可以近似看做一個(gè)回調(diào)函數(shù), 也就是說(shuō), 在jvm這邊, 你的suspend function, 最終變成了接受一個(gè)回調(diào)函數(shù)為參數(shù)的普通方法, 而且在其它平臺(tái)也大抵類似尖淘。
那么suspend function里面的suspend function調(diào)用是怎么調(diào)的呢?看上面的偽代碼著觉,你大概可以知道: 通過(guò)回調(diào), 在getToken的回調(diào)里面繼續(xù)調(diào)用postItems方法村生,但是狀態(tài)已經(jīng)改變,則可以執(zhí)行到下一個(gè)suspend掛起點(diǎn)饼丘,以此類推,通過(guò)這個(gè)狀態(tài)機(jī)實(shí)現(xiàn)了原來(lái)kotlin suspend 方法的
掛起與恢復(fù)
趁桃。
Kotlin 跨平臺(tái)實(shí)現(xiàn)
雖然Kotlin 編譯后的普通方法提供了回調(diào)的應(yīng)用, 但是正如官方文檔中所說(shuō)葬毫, 你永遠(yuǎn)不要嘗試在各個(gè)平臺(tái)實(shí)現(xiàn)這些回調(diào)镇辉, 除非你是個(gè) "coroutine master"
, 實(shí)現(xiàn)context等都是有坑的。
Kotlin 協(xié)程 node.js
kotlin.coroutines 有個(gè)專門的 promise 庫(kù)贴捡, 所以如果你要暴露一個(gè)api給外面的js庫(kù)去調(diào)用忽肛, 你可以這么寫:
suspend fun helloWorld() {
delay(1000)
return "1212"
}
fun theApiYouWantToExposeAndUsedInOtherJsModules() {
Globalscope.promise {
helloworld()
}
}
Kotlin 協(xié)程 jvm
Jvm 有runblocking 的api阻塞當(dāng)前線程執(zhí)行, 這個(gè)是一個(gè)優(yōu)點(diǎn)烂斋,因?yàn)樵趎ode.js單線程里面你沒(méi)辦法runblocking.
所以你可以暴露這樣的api:
suspend fun helloWorld() {
delay(1000)
return "1212"
}
fun theApiYouWantToExposeAndUsedInOtherJvmProgram() = runBlocking {
helloworld()
}
}
當(dāng)然為了性能更推薦的方式是利用jvm 獨(dú)有future api 返回一個(gè)Future 對(duì)象:
fun theApiYouWantToExposeAndUsedInOtherJvmProgram() = future {
helloworld()
}
}
Kotlin 協(xié)程 native
native 也有runblocking, 不過(guò)好像在swift 里面有個(gè)天坑屹逛。我對(duì)swift不是很了解, 解決方案給你扔這了: https://github.com/ktorio/ktor/issues/678
如果有問(wèn)題或者謬誤汛骂, 歡迎在評(píng)論指出罕模。