Kotlin Coroutines 筆記 (一)

Tony沈哲發表於2018-07-18

安靜的妹子.jpg

一. 協程

Kotlin 在1.1版本之後引入了協程的概念,目前它還是一個試驗的API。

在作業系統中,我們知道程式和執行緒的概念以及區別。而協程相比於執行緒更加輕量級,協程又稱微執行緒。

協程是一種使用者態的輕量級執行緒,協程的排程完全由使用者控制。協程擁有自己的暫存器上下文和棧。協程排程切換時,將暫存器上下文和棧儲存到其他地方,在切回來的時候,恢復先前儲存的暫存器上下文和棧,直接操作棧則基本沒有核心切換的開銷,可以不加鎖的訪問全域性變數,所以上下文的切換非常快。

Kotlin 的協程是無阻塞的非同步程式設計方式。Kotlin 允許我們使用協程來代替複雜的執行緒阻塞操作,並且複用原本的執行緒資源。

Kotlin 的協程是依靠編譯器實現的, 並不需要作業系統和硬體的支援。編譯器為了讓開發者編寫程式碼更簡單方便, 提供了一些關鍵字(例如suspend), 並在內部自動生成了一些支援型的程式碼。

先舉兩個例子來說明協程的輕量級,分別建立10w個協程和10w個執行緒進行測試。

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) {
            // 建立新的coroutine
            launch(CommonPool) {
                // 掛起當前上下文而非阻塞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個協程的建立在本機大約花費 1 秒,經過測試100w個協程的建立大約花費11 秒。

十萬個協程測試.jpeg

import kotlin.concurrent.thread

/**
 * Created by tony on 2018/7/18.
 */
fun main(args: Array<String>) {

    val start = System.currentTimeMillis()

    val threads = List(100000) {
        // 建立新的執行緒
        thread {
            Thread.sleep(1000)
            println(Thread.currentThread().name)
        }
    }

    threads.forEach { it.join() }

    val spend = (System.currentTimeMillis()-start)/1000

    println("Threads: spend= $spend s")

}
複製程式碼

十萬個執行緒測試.jpeg

10w個執行緒的建立出現了OutOfMemoryError。

二. 協程常用的基本概念

2.1 CoroutineContext

協程上下文,它包含了一個預設的協程排程器。所有協程都必須在 CoroutineContext 中執行。

2.2 CoroutineDispatcher

協程排程器,它用來排程和處理任務,決定了相關協程應該在哪個或哪些執行緒中執行。Kotlin 的協程包含了多種協程排程器。

2.3 Continuation

按照字面意思是繼續、持續的意思。協程的執行可能是分段執行的:先執行一段,掛起,再執行一段,再掛起......

Continuation 則表示每一段執行的程式碼,Continuation 是一個介面。

2.4 Job

任務執行的過程被封裝成 Job,交給協程排程器處理。Job 是一種具有簡單生命週期的可取消任務。Job 擁有三種狀態: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 完成時是沒有返回值的,Deferred 可以為任務完成時提供返回值,並且Deferred 新增了一個狀態 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 關鍵字

協程計算可以被掛起而無需阻塞執行緒。我們使用 suspend 關鍵字來修飾可以被掛起的函式。被標記為 suspend 的函式只能執行在協程或者其他 suspend 函式中。

suspend 可以修飾普通函式、擴充套件函式和 lambda 表示式。

三. 協程的多種使用方式

Kotlin 的協程支援多種非同步模型:

Kotlin協程支援的非同步模型.png

這些非同步機制在 Kotlin 的協程中都有實現。

Kotlin 官方對協程提供的三種級別的能力支援, 分別是: 最底層的語言層, 中間層標準庫(kotlin-stdlib), 以及最上層應用層(kotlinx.coroutines)。

3.1 協程的hello world版本

使用 launch 和 async 都能啟動一個新的協程。

    val job = launch {
        delay(1000)
        println("Hello World!")
    }

    Thread.sleep(2000)
複製程式碼

或者

    val deferred  = async {

        delay(1000)
        println("Hello World!")
    }

    Thread.sleep(2000)
複製程式碼

它們分別會返回一個 Job 物件和一個 Deferred 物件。

下面使用 runBlocking 來建立協程。

fun main(args: Array<String>) = runBlocking<Unit> {
    launch {
        delay(1000)
        println("Hello World!")
    }

    delay(2000)
}
複製程式碼

runBlocking 建立的協程直接執行在當前執行緒上,同時阻塞當前執行緒直到結束。

launch 和 async 在建立時可以使用不同的CoroutineDispatcher,例如:CommonPool。

在 runBlocking 內還可以建立其他協程,例如launch。反之則不行。

總結:

Kotlin 的協程能夠簡化非同步程式設計的程式碼,使用同步的方式實現非同步。協程的概念和理論比較多,第一篇只是一個開始,只整理了其中一些基本概念。


Java與Android技術棧:每週更新推送原創技術文章,歡迎掃描下方的公眾號二維碼並關注,期待與您的共同成長和進步。

Kotlin Coroutines 筆記 (一)

相關文章