Kotlin Coroutines 筆記 (一)

安靜的妹子.jpg

一. 協(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í),將寄存器上下文和棧保存到其他地方,在切回來的時(shí)候,恢復(fù)先前保存的寄存器上下文和棧,直接操作棧則基本沒有內(nèi)核切換的開銷,可以不加鎖的訪問全局變量,所以上下文的切換非常快。

Kotlin 的協(xié)程是無阻塞的異步編程方式。Kotlin 允許我們使用協(xié)程來代替復(fù)雜的線程阻塞操作,并且復(fù)用原本的線程資源。

Kotlin 的協(xié)程是依靠編譯器實(shí)現(xiàn)的, 并不需要操作系統(tǒng)和硬件的支持。編譯器為了讓開發(fā)者編寫代碼更簡單方便, 提供了一些關(guān)鍵字(例如suspend), 并在內(nèi)部自動(dòng)生成了一些支持型的代碼。

先舉兩個(gè)例子來說明協(xié)程的輕量級(jí),分別創(chuàng)建10w個(gè)協(xié)程和10w個(gè)線程進(jìn)行測試。

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)過測試100w個(gè)協(xié)程的創(chuàng)建大約花費(fèi)11 秒。

十萬個(gè)協(xié)程測試.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) {
        // 創(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")

}
十萬個(gè)線程測試.jpeg

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)度器,它用來調(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í)行的過程被封裝成 Job,交給協(xié)程調(diào)度器處理。Job 是一種具有簡單生命周期的可取消任務(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í)是沒有返回值的,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ì)算可以被掛起而無需阻塞線程。我們使用 suspend 關(guān)鍵字來修飾可以被掛起的函數(shù)。被標(biāo)記為 suspend 的函數(shù)只能運(yùn)行在協(xié)程或者其他 suspend 函數(shù)中。

suspend 可以修飾普通函數(shù)、擴(kuò)展函數(shù)和 lambda 表達(dá)式。

三. 協(xié)程的多種使用方式

Kotlin 的協(xié)程支持多種異步模型:


Kotlin協(xié)程支持的異步模型.png

這些異步機(jī)制在 Kotlin 的協(xié)程中都有實(shí)現(xiàn)。

Kotlin 官方對(duì)協(xié)程提供的三種級(jí)別的能力支持, 分別是: 最底層的語言層, 中間層標(biāo)準(zhǔn)庫(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 來創(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é)程能夠簡化異步編程的代碼,使用同步的方式實(shí)現(xiàn)異步。協(xié)程的概念和理論比較多,第一篇只是一個(gè)開始,只整理了其中一些基本概念。

該系列的相關(guān)文章:
Kotlin Coroutines 筆記 (二)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容