【Kotlin回顧】9.協程思維模型

語言: CN / TW / HK

theme: cyanosis


開啟掘金成長之旅!這是我參與「掘金日新計劃 · 12 月更文挑戰」的第9天,點選檢視活動詳情

1.協程

協程不是程序或執行緒,它的執行過程更類似於子例程或者說不帶返回值的函式呼叫。

一個程式可以包含多個協程,類似於一個程序包含多個執行緒。執行緒有自己的上下文多個執行緒存在時它們相對獨立,切換受系統控制,而協程也相對獨立,也有自己的上下文,但是切換是由自己控制的,當需要切換到其他協程時是由當前協程控制的。

| | 執行緒 | 協程 | | ------- | -------- | ---------- | | 獨立性 | 相對獨立 | 相對獨立 | | 上下文 | 有自己的上下文 | 有自己的上下文 | | 切換 | 系統決定是否切換 | 當前協程決定是否切換 |

2.Kotlin協程

1.引入Kotlin協程

Kotlin中如果要使用協程是需要新增依賴的,它沒有被整合在標準庫中,單獨拎出來主要是為了減小標準庫的體積

implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0'

那麼要如何理解Kotlin協程?Kotlin協程可以理解為更輕量級的執行緒,協程的執行離不開執行緒,這有點像執行緒和程序之間的關係。

2.Kotlin協程的使用

``` fun main() = runBlocking { doWorld() }

suspend fun doWorld() = coroutineScope {
launch { delay(1000L) println("World!") } println("Hello") }

//輸出結果: //Hello //World! ```

上面的程式碼是Kotlin協程的一個簡單例子其中runBlockingcoroutineScopelaunch是協程作用域,suspend是掛起,delay(1000L)是一個延時函式。從結果來看我們的程式碼順序和輸出結果的順序不一樣,這就是Kotlin協程的魅力,我們看下面的流程圖:

3.Kotlin協程的輕量(總結的還不夠清晰)

  • 啟動 10 億個執行緒,這樣的程式碼執行在大部分的機器上都是會因為記憶體不足等原因而異常退出的。那麼啟動10億個協程則不會出現異常因為協程是非常輕量的;
  • 協程雖然是執行線上程上的但是它並不會與某個執行緒繫結,而且還可以在不同執行緒之間切換。

4.協程的“非阻塞式”

執行緒是阻塞的,因為它在執行一個耗時任務時只有這個任務完成了才會執行後面的任務,而Kotlin在執行一個耗時任務時會把這個任務放入後臺執行,去執行下一個任務。

``` fun main() { repeat(3) { Thread.sleep(1000L) println("Print-1:${Thread.currentThread().name}") }

repeat(3) {
    Thread.sleep(900L)
    println("Print-2:${Thread.currentThread().name}")
}

}

/ 輸出結果: Print-1:main Print-1:main Print-1:main Print-2:main Print-2:main Print-2:main / ```

上面的程式碼是阻塞式任務,可以看到兩個任務之間的等待時差是100毫秒,但是第二個repeat執行的前提是第一個repeat執行完畢,那麼這個任務的最終耗時就是5700毫秒。

``` fun main() = runBlocking { launch { repeat(3) { delay(1000L) println("Print-1:${Thread.currentThread().name}") } }

launch {
    repeat(3) {
        delay(900L)
        println("Print-2:${Thread.currentThread().name}")
    }
}
delay(3000L)

}

/ 輸出結果: Print-2:main @coroutine#3 Print-1:main @coroutine#2 Print-2:main @coroutine#3 Print-1:main @coroutine#2 Print-2:main @coroutine#3 Print-1:main @coroutine#2 / ```

上面的程式碼是非阻塞式任務,可以看到兩個任務之間的等待時差是100毫秒,第以個repeat和第二個repeat是同時執行的,也就是說他們同時開始執行,當達到900毫秒時第二個repeat開始執行,當達到1000毫秒時第一個repeat開始執行,那麼這個任務的最終耗時就是3000毫秒。

由此可見,Kotlin 協程的“非阻塞”其實只是語言層面的,當我們呼叫 JVM 層面的 Thread.sleep() 的時候,它仍然會變成阻塞式的。與此同時,這也意味著我們在協程當中應該儘量避免出現阻塞式的行為。儘量使用 delay,而不是 sleep。

到這裡其實就會產生一個疑問,delay就能解決阻塞的問題嗎?答案是不是,解決阻塞問題的其實是Kotlin的掛起和恢復的能力,這是協程才擁有的特殊能力。

掛起和恢復又該怎麼理解呢,舉個例子,現在有兩件事情:①燒一壺水燒開後斷開電源;②做飯;第一件事是一個耗時任務,開始燒水後我去做第二件事這就是掛起,當水燒開以後要去斷開電源這就是恢復。

5.建立思維模型

Kotlin的協程要比Java執行緒更抽象,因為Java的執行緒可以找到Thread的原始碼,同時執行緒也是作業系統中的一個概念,所以理解起來較為簡單。而Kotlin的協程沒有類似的知識點可以建立關聯,所以在學習Kotlin協程的時候就需要簡歷協程的思維模型, 沒有這個思維理解Kotlin協程就比較難。

如何建立Kotlin的思維模型?可以將Kotlin協程想象成一個“更加輕量級的執行緒”。

從包含關係上看,協程跟執行緒的關係,有點像執行緒與程序的關係,畢竟協程不可能脫離執行緒執行。所以,協程可以理解為執行線上程當中的、更加輕量的 Task