一. 協(xié)程
Kotlin 在1.1版本之后引入了協(xié)程的概念,目前它還是一個(gè)試驗(yàn)的API。
在操作系統(tǒng)中大咱,我們知道進(jìn)程和線程的概念以及區(qū)別窒篱。而協(xié)程相比于線程更加輕量級(jí)焕刮,協(xié)程又稱微線程。
協(xié)程是一種用戶態(tài)的輕量級(jí)線程墙杯,協(xié)程的調(diào)度完全由用戶控制配并。協(xié)程擁有自己的寄存器上下文和棧。協(xié)程調(diào)度切換時(shí)高镐,將寄存器上下文和棧保存到其他地方溉旋,在切回來(lái)的時(shí)候,恢復(fù)先前保存的寄存器上下文和棧嫉髓,直接操作棧則基本沒(méi)有內(nèi)核切換的開(kāi)銷观腊,可以不加鎖的訪問(wèn)全局變量邑闲,所以上下文的切換非常快梧油。
Kotlin 的協(xié)程是無(wú)阻塞的異步編程方式苫耸。Kotlin 允許我們使用協(xié)程來(lái)代替復(fù)雜的線程阻塞操作,并且復(fù)用原本的線程資源儡陨。
Kotlin 的協(xié)程是依靠編譯器實(shí)現(xiàn)的, 并不需要操作系統(tǒng)和硬件的支持褪子。編譯器為了讓開(kāi)發(fā)者編寫代碼更簡(jiǎn)單方便, 提供了一些關(guān)鍵字(例如suspend
), 并在內(nèi)部自動(dòng)生成了一些支持型的代碼。
先舉兩個(gè)例子來(lái)說(shuō)明協(xié)程的輕量級(jí)骗村,分別創(chuàng)建10w個(gè)協(xié)程和10w個(gè)線程進(jìn)行測(cè)試嫌褪。
import kotlinx.coroutines.experimental.CommonPool
import kotlinx.coroutines.experimental.delay
import kotlinx.coroutines.experimental.launch
import kotlinx.coroutines.experimental.runBlocking
/**
* Created by tony on 2018/7/18.
*/
fun main(args: Array<String>) {
val start = System.currentTimeMillis()
runBlocking {
val jobs = List(100000) {
// 創(chuàng)建新的coroutine
launch(CommonPool) {
// 掛起當(dāng)前上下文而非阻塞1000ms
delay(1000)
println("thread name="+Thread.currentThread().name)
}
}
jobs.forEach {
it.join()
}
}
val spend = (System.currentTimeMillis()-start)/1000
println("Coroutines: spend= $spend s")
}
10w個(gè)協(xié)程的創(chuàng)建在本機(jī)大約花費(fèi) 1 秒,經(jīng)過(guò)測(cè)試100w個(gè)協(xié)程的創(chuàng)建大約花費(fèi)11 秒胚股。
import kotlin.concurrent.thread
/**
* Created by tony on 2018/7/18.
*/
fun main(args: Array<String>) {
val start = System.currentTimeMillis()
val threads = List(100000) {
// 創(chuàng)建新的線程
thread {
Thread.sleep(1000)
println(Thread.currentThread().name)
}
}
threads.forEach { it.join() }
val spend = (System.currentTimeMillis()-start)/1000
println("Threads: spend= $spend s")
}
10w個(gè)線程的創(chuàng)建出現(xiàn)了OutOfMemoryError。
二. 協(xié)程常用的基本概念
2.1 CoroutineContext
協(xié)程上下文晃痴,它包含了一個(gè)默認(rèn)的協(xié)程調(diào)度器财忽。所有協(xié)程都必須在 CoroutineContext 中執(zhí)行倘核。
2.2 CoroutineDispatcher
協(xié)程調(diào)度器,它用來(lái)調(diào)度和處理任務(wù)即彪,決定了相關(guān)協(xié)程應(yīng)該在哪個(gè)或哪些線程中執(zhí)行。Kotlin 的協(xié)程包含了多種協(xié)程調(diào)度器隶校。
2.3 Continuation
按照字面意思是繼續(xù)、持續(xù)的意思深胳。協(xié)程的執(zhí)行可能是分段執(zhí)行的:先執(zhí)行一段绰疤,掛起,再執(zhí)行一段舞终,再掛起......
Continuation 則表示每一段執(zhí)行的代碼,Continuation 是一個(gè)接口敛劝。
2.4 Job
任務(wù)執(zhí)行的過(guò)程被封裝成 Job,交給協(xié)程調(diào)度器處理蛾方。Job 是一種具有簡(jiǎn)單生命周期的可取消任務(wù)。Job 擁有三種狀態(tài):isActive桩砰、isCompleted、isCancelled五芝。
wait children
+-----+ start +--------+ complete +-------------+ finish +-----------+
| New | ---------------> | Active | -----------> | Completing | -------> | Completed |
+-----+ +--------+ +-------------+ +-----------+
| | |
| cancel | cancel | cancel
V V |
+-----------+ finish +------------+ |
| Cancelled | <--------- | Cancelling | <----------------+
|(completed)| +------------+
+-----------+
2.5 Deferred
Deferred 是 Job 的子類枢步。Job 完成時(shí)是沒(méi)有返回值的渐尿,Deferred 可以為任務(wù)完成時(shí)提供返回值,并且Deferred 新增了一個(gè)狀態(tài) isCompletedExceptionally隘擎。
wait children
+-----+ start +--------+ complete +-------------+ finish +-----------+
| New | ---------------> | Active | ----------> | Completing | ---+-> | Resolved |
+-----+ +--------+ +-------------+ | |(completed)|
| | | | +-----------+
| cancel | cancel | cancel |
V V | | +-----------+
+-----------+ finish +------------+ | +-> | Failed |
| Cancelled | <--------- | Cancelling | <---------------+ |(completed)|
|(completed)| +------------+ +-----------+
+-----------+
2.6 suspend 關(guān)鍵字
協(xié)程計(jì)算可以被掛起而無(wú)需阻塞線程凉夯。我們使用 suspend 關(guān)鍵字來(lái)修飾可以被掛起的函數(shù)。被標(biāo)記為 suspend 的函數(shù)只能運(yùn)行在協(xié)程或者其他 suspend 函數(shù)中震桶。
suspend 可以修飾普通函數(shù)征绎、擴(kuò)展函數(shù)和 lambda 表達(dá)式。
三. 協(xié)程的多種使用方式
Kotlin 的協(xié)程支持多種異步模型:
這些異步機(jī)制在 Kotlin 的協(xié)程中都有實(shí)現(xiàn)人柿。
Kotlin 官方對(duì)協(xié)程提供的三種級(jí)別的能力支持, 分別是: 最底層的語(yǔ)言層, 中間層標(biāo)準(zhǔn)庫(kù)(kotlin-stdlib), 以及最上層應(yīng)用層(kotlinx.coroutines)。
3.1 協(xié)程的hello world版本
使用 launch 和 async 都能啟動(dòng)一個(gè)新的協(xié)程江咳。
val job = launch {
delay(1000)
println("Hello World!")
}
Thread.sleep(2000)
或者
val deferred = async {
delay(1000)
println("Hello World!")
}
Thread.sleep(2000)
它們分別會(huì)返回一個(gè) Job 對(duì)象和一個(gè) Deferred 對(duì)象哥放。
下面使用 runBlocking 來(lái)創(chuàng)建協(xié)程。
fun main(args: Array<String>) = runBlocking<Unit> {
launch {
delay(1000)
println("Hello World!")
}
delay(2000)
}
runBlocking 創(chuàng)建的協(xié)程直接運(yùn)行在當(dāng)前線程上东臀,同時(shí)阻塞當(dāng)前線程直到結(jié)束犀农。
launch 和 async 在創(chuàng)建時(shí)可以使用不同的CoroutineDispatcher,例如:CommonPool。
在 runBlocking 內(nèi)還可以創(chuàng)建其他協(xié)程轨奄,例如launch拒炎。反之則不行。
總結(jié):
Kotlin 的協(xié)程能夠簡(jiǎn)化異步編程的代碼击你,使用同步的方式實(shí)現(xiàn)異步丁侄。協(xié)程的概念和理論比較多,第一篇只是一個(gè)開(kāi)始鸿摇,只整理了其中一些基本概念。
該系列的相關(guān)文章:
Kotlin Coroutines 筆記 (二)