Kotlin 的Coroutine
前面說(shuō)過(guò)傳統(tǒng)web框架Tomcat采用的多線程方式,當(dāng)請(qǐng)求接入服務(wù)器時(shí),Tomcat會(huì)為每個(gè)請(qǐng)求鏈接分配一個(gè)線程。當(dāng)請(qǐng)求不是很多的時(shí)候,系統(tǒng)不會(huì)出現(xiàn)問(wèn)題,請(qǐng)求數(shù)目超過(guò)最大分配線程數(shù),這個(gè)時(shí)候會(huì)有多個(gè)線程阻塞住,會(huì)出現(xiàn)一些問(wèn)題。
多線程在執(zhí)行的時(shí)候,知識(shí)看上去是同時(shí)執(zhí)行,因?yàn)榫€程執(zhí)行是通過(guò)cpu來(lái)進(jìn)行調(diào)度的,cpu通過(guò)在每個(gè)線程之間來(lái)回切換,使其看上去是同時(shí)執(zhí)行的一樣。其實(shí)cpu在某個(gè)時(shí)間片內(nèi)只能執(zhí)行一個(gè)線程,當(dāng)這個(gè)線程執(zhí)行一會(huì)它就會(huì)去執(zhí)行其他線程。當(dāng)cpu從一個(gè)線程切換到另一個(gè)線程時(shí)候會(huì)執(zhí)行如下操作:
- 保存當(dāng)前執(zhí)行線程的執(zhí)行上下文
- 載入另一個(gè)線程的執(zhí)行上下文
協(xié)程是一個(gè)無(wú)優(yōu)先級(jí)的調(diào)度組件,允許子程序在特定地方掛起恢復(fù)。
線程包含于進(jìn)程,協(xié)程包含于線程。
只要內(nèi)存夠用,一個(gè)線程中可以有任意多個(gè)協(xié)程,但某一時(shí)刻只能由一個(gè)協(xié)程運(yùn)行,多個(gè)協(xié)程該線程分配到的計(jì)算機(jī)資源。
fun main(args: Array<String>) {
GlobalScope.launch { // 在后臺(tái)啟動(dòng)一個(gè)協(xié)程
delay(1000L) // 延遲一秒(非阻塞)
println("World!") // 延遲之后輸出
}
println("Hello,") // 協(xié)程被延遲了一秒,但是主線程繼續(xù)執(zhí)行
Thread.sleep(2000L) // 為了使JVM?;?,阻塞主線程2秒鐘(這段代碼刪掉會(huì)出現(xiàn)什么情況???)
}
launch 構(gòu)造了一個(gè)協(xié)程,該協(xié)程內(nèi)部調(diào)用了delay方法,該方法會(huì)掛起協(xié)程,但是不會(huì)阻塞線程,所以在協(xié)程推遲1s的時(shí)間,線程中的“Hello”會(huì)先輸出,然后“World!”才會(huì)輸出
協(xié)程的切換可以通過(guò)程序自己控制,是工作在線程之上的,不需要操作系統(tǒng)調(diào)度,理論上降低了開銷。
1. launch 與 runBlocking
上面的例子只是為了說(shuō)明線程和協(xié)程理論的相似處,存在不合理的地方。我們既使用了delay方法,又使用了sleep方法。
- delay 只能在協(xié)程中使用,用于掛起協(xié)程,不會(huì)阻塞線程。
- sleep 用來(lái)阻塞線程
fun main(args: Array<String>) = runBlocking{
launch { // 在后臺(tái)啟動(dòng)一個(gè)協(xié)程
delay(1000L) // 延遲一秒(非阻塞)
println("World!") // 延遲之后輸出
}
println("Hello,") // 協(xié)程被延遲了一秒,但是主線程繼續(xù)執(zhí)行
Thread.sleep(2000L) // 為了使JVM?;睿枞骶€程2秒鐘(這段代碼刪掉會(huì)出現(xiàn)什么情況???)
}
這個(gè)例子和上面的基本沒(méi)有變化,結(jié)果也一樣,我們看到兩個(gè)函數(shù)
launch和runBlocking 這兩個(gè)函數(shù)都會(huì)啟動(dòng)一個(gè)協(xié)程,不同的是runBlocking啟動(dòng)一個(gè)主協(xié)程,launch啟動(dòng)的協(xié)程能在runBlocking中運(yùn)行(反過(guò)來(lái)不行)。runBlocking 方法仍舊會(huì)阻塞當(dāng)前執(zhí)行的線程。
2. 協(xié)程的生命周期與join
delay和sleep都是延遲執(zhí)行,目的都是?;畛绦?。
但是我們執(zhí)行IO操作的時(shí)候并不知道操作執(zhí)行多久,沒(méi)法設(shè)定一個(gè)時(shí)間讓程序?;睿瑸榱俗寘f(xié)程執(zhí)行完畢前一直?;?,我們可以使用join
fun main(args: Array<String>) = runBlocking {
val job = launch {
search()
}
println("Hello,")
job.join()
}
suspend fun search() {
delay(1000L)
println("World!")
}
出現(xiàn)了一個(gè)新的關(guān)鍵詞suspend。用suspend的方法,和其他方法沒(méi)有大的區(qū)別,不同的是suspend方法內(nèi)部還可以調(diào)用其他suspend修飾的方法,只能在協(xié)程內(nèi)部或其他suspend方法中執(zhí)行,普通方法則不行。delay就是一個(gè)suspend修飾的方法。
fun main(args: Array<String>) = runBlocking<Unit> {
val one = searchItemlOne()
val two = searchItemTwo()
println("The items is ${one} and ${two}")
}
fun main(args: Array<String>) = runBlocking<Unit> {
val one = async { searchItemlOne() }
val two = async { searchItemTwo() }
println("The items is ${one.await()} and ${two.await()}")
}
再看下上面的例子,執(zhí)行結(jié)果是一樣的,第一個(gè)在協(xié)程內(nèi)部執(zhí)行是順序的,類似java中的同步,限執(zhí)行searchItemlOne在執(zhí)行searchItemTwo。為了讓兩個(gè)方法并行執(zhí)行,使用Kotlin中的async與await。async類似前面的launch都是創(chuàng)建了一個(gè)子協(xié)程,不同的是async有返回值,返回Deferred對(duì)象。
Deferred 是一個(gè)非阻塞可以取消的future,是一個(gè)帶有結(jié)果的job
launch 也會(huì)返回一個(gè)job對(duì)象,但是沒(méi)有返回值
我們還用到了await,future是非阻塞,意思將來(lái)會(huì)有一個(gè)結(jié)果返回。await可以等待這個(gè)值查詢到后,將它獲取出來(lái)。
fun main() = runBlocking{
val time = measureTimeMillis {
val one = searchItemlOne()
val two = searchItemTwo()
println("The items is ${one} and ${two}")
}
println("Cost time is ${time} ms")
}
The items is item-one and item-two
Cost time is 2034 ms
再看并行
fun main(args: Array<String>) = runBlocking{
val time = measureTimeMillis {
val one = async { searchItemlOne() }
val two = async { searchItemTwo() }
println("The items is ${one.await()} and ${two.await()}")
}
println("Cost time is ${time} ms")
}
The items is item-one and item-two
Cost time is 1170 ms
從理論代碼看不出差別,但是從時(shí)間打印來(lái)看,并行效果就明顯了。