提到多線程鲤竹,寫慣了 Kotlin/JVM 的可能第一個(gè)反應(yīng)就是 thread { ... }
拘泞,畢竟 Kotlin 已經(jīng)為我們設(shè)計(jì)好了很多東西疯汁,然而在 Kotlin/Native 上涕蚤,卻是不存在這樣的東西的歉糜。通常情況下乘寒, C 程序員會(huì)很熟悉 pthread
,并且我們也可以在 Kotlin/Native 上實(shí)現(xiàn)類似的功能匪补。
由于 cinterop
的存在伞辛,使得我們可以直接調(diào)用 C 的標(biāo)準(zhǔn)函數(shù)庫,寫出來的 pthread
代碼是這樣的:
memScoped {
val thread = alloc<pthread_tVar>()
pthread_create(thread.ptr, null, staticCFunction { argc ->
initRuntimeIfNeeded()
... ...
null // as COpaquePointer?
}, null)
pthread_join(thread.value, null)
}
可能從上一篇文章起夯缺,大家就對類似于 pthread_tVar
或是 IntVar
這類寫法表示疑問蚤氏,這些類型是怎么來的呢,其實(shí)在 cinterop
擁有一種左值約定
踊兜,我貼個(gè)原文大家看一下竿滨。
Also, any C type has the Kotlin type representing the lvalue of this type, i.e., the value located in memory rather than a simple immutable self-contained value. Think C++ references, as a similar concept. For structs (and typedef
s to structs) this representation is the main one and has the same name as the struct itself, for Kotlin enums it is named ${type}Var
, for CPointer<T>
it is CPointerVar<T>
, and for most other types it is ${type}Var
.
總的來說,就是 C 類型后面加 Var
就是 Kotlin 內(nèi)的類型了捏境。
一般來說于游,只要是 C 可以實(shí)現(xiàn)的,可以用非常平滑的方法遷移到 Kotlin/Native垫言。
下面說一下 Kotlin/Native 原生實(shí)現(xiàn)的線程模型贰剥,相比于 pthread
的 API,原生實(shí)現(xiàn)的 Worker
更符合 Kotlin 的代碼習(xí)慣骏掀,也擁有更好的可讀性鸠澈。
如以下例子:
val str = "hello"
Worker.start().execute(TransferMode.SAFE, { str }) { it }.consume { println(it) }
這段代碼的意思很簡單,在 execute()
方法傳入?yún)?shù)并且啟動(dòng)生產(chǎn)截驮,該生產(chǎn)過程是異步的笑陈,完成后調(diào)用 consume()
進(jìn)行消費(fèi),消費(fèi)的過程是同步的葵袭。
這里會(huì)有一個(gè)需要非常注意的地方涵妥,不能偷懶,比如說以下代碼:
val str = "hello"
Worker.start().execute(TransferMode.SAFE, { }) { str }.consume { println(str) }
是不是看著沒問題? 但是實(shí)際編譯會(huì)報(bào)錯(cuò)坡锡,異常信息如下:
Worker.execute must take an unbound, non-capturing function or lambda
在這里需要注意的是蓬网,execute
方法的定義:
public final fun <T1, T2> execute(mode: TransferMode, producer: () -> T1, @VolatileLambda job: (T1) -> T2): Future<T2>
在 job
參數(shù)前有一個(gè) @VolatileLambda
的注解,這就表明了 job
所對應(yīng)的函數(shù)不允許有 綁定
或 捕獲
的行為鹉勒,而直接傳入 str
變量顯然就是捕獲了帆锋。
所以必須在 producer
參數(shù)中予以傳參,知道這一點(diǎn)就可以做很多事情了禽额,比如說執(zhí)行一個(gè)外部的命令:
fun main(args: Array<String>) {
if (args.isEmpty()) return
val cmd = args[0]
Worker.start().execute(TransferMode.SAFE, { cmd }) {
runCommand(it)
}.consume {
println("output => ${it.output}\nerror => ${it.error}")
}
}
其中 runCommand()
的代碼如下:
import kotlinx.cinterop.*
import platform.posix.*
data class CommandResult(val output: String, val error: String)
fun runCommand(cmd: String) = memScoped {
var ret = ""
var err = ""
val size = 1024
val buf = allocArray<ByteVar>(size)
val fst = popen(cmd, "r")
if (fst != null) {
while (fgets(buf, size, fst) != null) {
ret += buf.toKString()
}
} else {
err = strerror(errno)?.toKString() ?: ""
}
pclose(fst)
CommandResult(ret, err)
}
知道了這些之后锯厢,就可以愉快的玩轉(zhuǎn)多線程了皮官。順便,Kotlin 還主打協(xié)程实辑,在這里也一起提一下捺氢。
要使用協(xié)程,必須在 build.gradle
內(nèi)引用協(xié)程相關(guān)的庫:
dependencies {
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core-common:1.1.1'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core-native:1.1.1'
}
這里要注意版本號(hào)剪撬,如果使用 Kotlin 1.3.21
摄乒,那么協(xié)程庫版本號(hào)對應(yīng)為 1.1.1
,如果使用 Kotlin 1.3.31
残黑,則對應(yīng)為 1.2.1
馍佑。如果版本號(hào)不匹配,會(huì)引起編譯異常梨水。
然后我們需要自己定義一個(gè) CoroutineScope
挤茄,這里只是做演示用,在這個(gè) Scope 內(nèi)不做其他事冰木,可以寫成這樣:
private class MyScope: CoroutineScope {
private val dispatcher = object : CoroutineDispatcher() {
override fun dispatch(context: CoroutineContext, block: Runnable) {
block.run()
}
}
private val job = Job()
override val coroutineContext: CoroutineContext get() = dispatcher + job
}
然后上面的 runCommand()
代碼就可以改成這樣:
MyScope().launch {
val ret = runCommand(cmd)
println("output => ${ret.output}\nerror => ${ret.error}")
}
順便一提,原先定義的 runCommand
是一個(gè)常規(guī)方法笼恰,在協(xié)程里用也可以將其改為 suspend
方法:
suspend fun runCommand(cmd: String): CommandResult { ... }