目錄
1、協程
2、依賴
3、協程啟動的三種方式
3.1、runBlocking:T
3.2、launch:Job
3.3、aync/await
4、GlobalScope
5、delay()與sleep()
6、協程的優點:
7、協程的缺點:
8、适用場景
9、子程式
10、程序
11、線程
1、協程
協程,又稱微線程。英文名Coroutine。
官方文檔定義:
協程通過将複雜性放入庫來簡化異步程式設計。程式的邏輯可以在協程中順序地表達,而底層庫會為我們解決其異步性。該庫可以将開發者代碼的相關部分包裝為回調、訂閱相關事件、在不同線程(甚至不同機器)上排程執行,而代碼則保持如同順序執行一樣簡單。
協程的開發人員 Roman Elizarov 是這樣描述協程的:協程就像非常輕量級的線程。線程是由系統排程的,線程切換或線程阻塞的開銷都比較大。而協程依賴于線程,但是協程挂起時不需要阻塞線程,幾乎是無代價的,協程是由開發者控制的。是以協程也像使用者态的線程,非常輕量級,一個線程中可以建立任意個協程。
總而言之:協程可以簡化異步程式設計,可以順序地表達程式,協程也提供了一種避免阻塞線程并用更廉價、更可控的操作替代線程阻塞的方法 -- 協程挂起。
2、依賴
github位址
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.3'
3、協程啟動的三種方式
3.1、runBlocking:T
運作一個新的協程并阻塞目前線程,直到目前協程完成為止。
它相當于線程和協程之間的一個排程,将線程排程到協程中,直到協程完成,在調回到線程中。
這種方式不推薦開啟一個協程,它是供在main函數和測試中使用。
從下面的輸出結果可以明顯看出,runBlocking:T 阻塞了目前Thread的執行。
fun main() {
Thread(Runnable {
repeat(5) {
if (it == 2) {
runBlocking {
repeat(5){
println("runBlocking的it=$it")
}
}
}
println("Thread線程的it=$it")
}
}).start()
}
//輸出結果
Thread線程的it=0
Thread線程的it=1
runBlocking的it=0
runBlocking的it=1
runBlocking的it=2
runBlocking的it=3
runBlocking的it=4
Thread線程的it=2
Thread線程的it=3
Thread線程的it=4
3.2、launch:Job
在不阻塞目前線程的情況下啟動新的協程,并以[Job]傳回對協程的引用。
當調用Job.cancel時,協程被取消。
從下面代碼可以看出,協程job1可以通過cancel()函數來取消,這就是協程的一大優點,可以主動的控制協程開啟和結束。被cancel()的協程無法再次重新開機。
fun main() {
val job1 = GlobalScope.launch {
repeat(5) {
println("launch的it=$it")
delay(500)
}
}
println("job.isCompleted=${job1.isCancelled}")
Thread.sleep(1500)
job1.cancel()
println("job.isCompleted=${job1.isCancelled}")
job1.start()
}
//輸出結果
job.isCompleted=false
launch的it=0
launch的it=1
launch的it=2
job.isCompleted=true
3.3、aync/await
建立一個協程并作為[Deferred]的實作傳回其将來的結果。
Deferred.await()可以擷取到你傳回的值。
fun main() = runBlocking {
val name1:Deferred<String> = async {
val name = "ZhangSan"
name
}
println("name=${name1.await()}")
}
//輸出結果
name=ZhangSan
4、GlobalScope
全局範圍内啟動在整個應用程式生命周期内運作且不會過早取消的頂級協程。
協程啟動通常應使用應用程式定義的[CoroutineScope]。 不建議在[GlobalScope]執行個體上使用[async] [CoroutineScope.async]或[launch] [CoroutineScope.launch]。
GlobalScope.launch {
repeat(10) {
delay(500)
println("launch的it=$it")
}
}
5、delay()與sleep()
delay() 不阻塞線程
将協程延遲給定時間而不阻塞線程,并在指定時間後恢複它。
此暫停功能可以取消。
Thread.sleep() 阻塞線程
使目前正在執行的線程進入休眠狀态(暫時停止執行)達指定的毫秒數。
6、協程的優點:
協程更加輕量,建立成本更小,降低了記憶體消耗
每個協程的體積比線程要小得多,是以一個程序可以容納數量相當可觀的協程。
協程是由開發者排程,減少了使用線程造成 CPU 上下文切換的開銷,提高了 CPU 緩存命中率
協作式排程相比搶占式排程的優勢在于上下文切換開銷更少、更容易把緩存跑熱。和多線程比,線程數量越多,協程的性能優勢就越明顯。程序 / 線程的切換需要在核心完成,而協程不需要,協程通過使用者态棧實作,更加輕量,速度更快。在重 I/O 的程式裡有很大的優勢。比如爬蟲裡,開幾百個線程會明顯拖慢速度,但是開協程不會。
但協程也放棄了原生線程的優先級概念,如果存在一個較長時間的計算任務,由于核心排程器總是優先 IO 任務,使之盡快得到響應,就将影響到 IO 任務的響應延時。假設這個線程中有一個協程是 CPU 密集型的他沒有 IO 操作,也就是自己不會主動觸發排程器排程的過程,那麼就會出現其他協程得不到執行的情況,是以這種情況下需要程式員自己避免。
此外,單線程的協程方案并不能從根本上避免阻塞,比如檔案操作、記憶體缺頁,這都屬于影響到延時的因素。
減少同步加鎖,整體上提高了性能
協程方案基于事件循環方案,減少了同步加鎖的頻率。但若存在競争,并不能保證臨界區,是以該上鎖的地方仍需要加上協程鎖。
可以按照同步思維寫異步代碼,即用同步的邏輯,寫由協程排程的回調
需要注意的是,協程的确可以減少 callback 的使用但是不能完全替換 callback。基于事件驅動的程式設計裡面反而不能發揮協程的作用而用 callback 更适合。
7、協程的缺點:
- 在協程執行中不能有線程的阻塞操作,否則整個線程被阻塞(協程是語言級别的,線程,程序屬于作業系統級别)
fun main(){
runBlocking {
launch {
repeat(5) {
delay(200)
println("我是Launche---$it----${Date().time}")
}
}
async {
repeat(5) {
delay(200)
if (it == 2) {
Thread.sleep(5000)
}
println("我是async---$it----${Date()}")
}
}
}
}
//輸出結果
我是Launche---0----Wed Dec 25 18:19:21 CST 2019
我是async---0----Wed Dec 25 18:19:21 CST 2019
我是Launche---1----Wed Dec 25 18:19:22 CST 2019
我是async---1----Wed Dec 25 18:19:22 CST 2019
我是Launche---2----Wed Dec 25 18:19:22 CST 2019
----------------------------------------------中間阻塞了5秒
我是async---2----Wed Dec 25 18:19:27 CST 2019
我是Launche---3----Wed Dec 25 18:19:27 CST 2019
我是async---3----Wed Dec 25 18:19:27 CST 2019
我是Launche---4----Wed Dec 25 18:19:27 CST 2019
我是async---4----Wed Dec 25 18:19:27 CST 2019
- 需要特别關注全局變量、對象引用的使用
協程可以處理 IO 密集型程式的效率問題,但是處理 CPU 密集型不是它的長處。
假設這個線程中有一個協程是 CPU 密集型的他沒有 IO 操作,也就是自己不會主動觸發排程器排程的過程,那麼就會出現其他協程得不到執行的情況,是以這種情況下需要程式員自己避免。
8、适用場景
- 高性能計算,犧牲公平性換取吞吐。協程最早來自高性能計算領域的成功案例,協作式排程相比搶占式排程而言,可以在犧牲公平性時換取吞吐
IO Bound 的任務
在 IO 密集型的程式中由于 IO 操作遠遠小于 CPU 的操作,是以往往需要 CPU 去等 IO 操作。同步 IO 下系統需要切換線程,讓作業系統可以再 IO 過程中執行其他的東西。這樣雖然代碼是符合人類的思維習慣但是由于大量的線程切換帶來了大量的性能的浪費。
是以人們發明了異步 IO。就是當資料到達的時候觸發我的回調。來減少線程切換帶來性能損失。但是這樣的壞處也是很大的,最大的問題就是破壞掉了人類這種線性的思維模式,你必須把一個邏輯上線性的過程切分成若幹個片段,每個片段的起點和終點就是異步事件的完成和開始。固然經過一些訓練你可以适應這種思維模式,但你還是要付出額外的心智負擔。與人類的思維模式相對應,大多數流行的程式設計語言都是指令式的,程式本身呈現出一個大緻的線性結構。異步回調在破壞點思維連貫性的同時也破壞掉了程式的連貫性,讓你在閱讀程式的時候花費更多的精力。這些因素對于一個軟體項目來說都是額外的維護成本,是以大多數公司并不是很青睐 node.js 或者 RxJava 之類的異步回調架構,盡管這些架構能提升程式的并發能力。
但是協程可以很好解決這個問題。比如把一個 IO 操作 寫成一個協程。當觸發 IO 操作的時候就自動讓出 CPU 給其他協程。要知道協程的切換很輕的。協程通過這種對異步 IO 的封裝既保留了性能也保證了代碼的容易編寫和可讀性。
Generator 式的流式計算
消除 Callback Hell(回調地獄),使用同步模型降低開發成本的同時保留更靈活控制流的好處,
9、子程式
子程式,或者稱為函數,在所有語言中都是層級調用,比如A調用B,B在執行過程中又調用了C,C執行完畢傳回,B執行完畢傳回,最後是A執行完畢。
是以子程式調用是通過棧實作的,一個線程就是執行一個子程式。
子程式調用總是一個入口,一次傳回,調用順序是明确的。而協程的調用和子程式不同。
協程看上去也是子程式,但執行過程中,在子程式内部可中斷,然後轉而執行别的子程式,在适當的時候再傳回來接着執行。
注意:在一個子程式中中斷,去執行其他子程式,不是函數調用,有點類似CPU的中斷。
10、程序
程序(Process)是計算機中的程式關于某資料集合上的一次運作活動,是系統進行資源配置設定和排程的基本機關,是作業系統結構的基礎。在早期面向程序設計的計算機結構中,程序是程式的基本執行實體;在當代面向線程設計的計算機結構中,程序是線程的容器。
程式是指令、資料及其組織形式的描述,程序是程式的實體。
程序的概念主要有兩點:
第一,程序是一個實體。每一個程序都有它自己的位址空間,一般情況下,包括文本區域(text region)、資料區域(data region)和堆棧(stack region)。文本區域存儲處理器執行的代碼;資料區域存儲變量和程序執行期間使用的動态配置設定的記憶體;堆棧區域存儲着活動過程調用的指令和本地變量。
第二,程序是一個“執行中的程式”。程式是一個沒有生命的實體,隻有處理器賦予程式生命時(作業系統執行之),它才能成為一個活動的實體,我們稱其為程序。
11、線程
線程(英語:thread)是作業系統能夠進行運算排程的最小機關。它被包含在程序之中,是程序中的實際運作機關。一條線程指的是程序中一個單一順序的控制流,一個程序中可以并發多個線程,每條線程并行執行不同的任務。
同一程序中的多條線程将共享該程序中的全部系統資源,如虛拟位址空間,檔案描述符和信号處理等等。但同一程序中的多個線程有各自的調用棧(call stack),自己的寄存器環境(register context),自己的線程本地存儲(thread-local storage)。
一個程序可以有很多線程,每條線程并行執行不同的任務。