Kotlin協程通信機制: Channel

来源:https://www.cnblogs.com/mengdd/archive/2019/12/03/kotlin-coroutines-channels.html

協程中的Channel用於協程間的通信, 它的宗旨是: ``` Do not communicate by sharing memory; instead, share memory by communicating. ``` ...


Coroutines Channels

Java中的多線程通信, 總會涉及到共用狀態(shared mutable state)的讀寫, 有同步, 死鎖等問題要處理.

協程中的Channel用於協程間的通信, 它的宗旨是:

Do not communicate by sharing memory; instead, share memory by communicating.

Channel basics

channels用於協程間的通信, 允許我們在不同的協程間傳遞數據(a stream of values).

生產者-消費者模式

發送數據到channel的協程被稱為producer, 從channel接受數據的協程被稱為consumer.

生產: send, produce.
消費: receive, consume.

當需要的時候, 多個協程可以向同一個channel發送數據, 一個channel的數據也可以被多個協程接收.

當多個協程從同一個channel接收數據的時候, 每個元素僅被其中一個consumer消費一次. 處理元素會自動將其從channel里刪除.

Channel的特點

Channel在概念上有點類似於BlockingQueue, 元素從一端被加入, 從另一端被消費. 關鍵的區別在於, 讀寫的方法不是blocking的, 而是suspending的.
在為空或為滿時. channel可以suspend它的sendreceive操作.

Channel的關閉和迭代

Channel可以被關閉, 說明沒有更多的元素了.
取消producer協程也會關閉channel.

在receiver端有一種方便的方式來接收: 用for迭代.

看這個例子:

fun main() = runBlocking<Unit> {
    val channel = Channel<Int>()
    launch {
        for (x in 1..5) channel.send(x)
        channel.close() // we're done sending
    }
// here we print received values using `for` loop (until the channel is closed)
    for (y in channel) println(y)
    println("Done!")
}

運行後會輸出:

1
2
3
4
5
Done!

Process finished with exit code 0

如果註釋掉channel.close()就會變成:

1
2
3
4
5

Done沒有被輸出, 程式也沒有退出, 這是因為接受者協程還在一直等待.

不同的Channel類型

庫中定義了多個channel類型, 它們的主要區別在於:

  • 內部可以存儲的元素數量;
  • send是否可以被掛起.

所有channel類型的receive方法都是同樣的行為: 如果channel不為空, 接收一個元素, 否則掛起.

Channel的不同類型:

  • Rendezvous channel: 0尺寸buffer, sendreceive要meet on time, 否則掛起. (預設類型).
  • Unlimited channel: 無限元素, send不被掛起.
  • Buffered channel: 指定大小, 滿了之後send掛起.
  • Conflated channel: 新元素會覆蓋舊元素, receiver只會得到最新元素, send永不掛起.

創建channel:

val rendezvousChannel = Channel<String>()
val bufferedChannel = Channel<String>(10)
val conflatedChannel = Channel<String>(CONFLATED)
val unlimitedChannel = Channel<String>(UNLIMITED)

預設是Rendezvous channel.

練習: 分析代碼輸出

看這段代碼:

fun main() = runBlocking<Unit> {
    val channel = Channel<String>()
    launch {
        channel.send("A1")
        channel.send("A2")
        log("A done")
    }
    launch {
        channel.send("B1")
        log("B done")
    }
    launch {
        repeat(3) {
            val x = channel.receive()
            log(x)
        }
    }
}

fun log(message: Any?) {
    println("[${Thread.currentThread().name}] $message")
}

這段代碼創建了一個channel, 傳遞String類型的元素.
兩個producder協程, 分別向channel發送不同的字元串, 發送完畢後列印各自的"done".
一個receiver協程, 接收channel中的3個元素並列印.

程式的運行輸出結果會是怎樣呢?

記得在Configurations中加上VM options: -Dkotlinx.coroutines.debug. 可以看到協程信息.

答案揭曉:

[main @coroutine#4] A1
[main @coroutine#4] B1
[main @coroutine#2] A done
[main @coroutine#3] B done
[main @coroutine#4] A2

答對了嗎?

為什麼會是這樣呢? 原因主要有兩點:

  • 這裡創建的channel是預設的Rendezvous類型, 沒有buffer, send和receive必須要meet, 否則掛起.
  • 兩個producer和receiver協程都運行在同一個線程上, ready to be resumed也只是加入了一個等待隊列, resume要按順序來.

這個例子在Introduction to Coroutines and Channels中有一個視頻解說.

另外, 官方文檔中還有一個ping-pang的例子, 為了說明Channels are fair.

參考

歡迎關註微信公眾號: 聖騎士Wind
微信公眾號


您的分享是我們最大的動力!

更多相關文章
  • spark 各個版本的application 調度演算法還是有這明顯的不同之處的。從spark1.3.0 到 spark 1.6.1、spark2.x 到 現在最新的spark 3.x ,調度演算法有了一定的修改。下麵大家一起學習一下,最新的spark 版本spark-3.0的Application 調 ...
  • 說明: 1)該實驗所有過程均是本人親自敲命令完成,所有代碼運行正確 2)安裝過程使用的是suse11 sp3操作系統,後續的實驗過程換成了麒麟中標,因此部分路徑可能存在差異 3)安裝過程使用了命令行安裝,圖形界面簡單,因此本文沒有介紹 4)job部分命令行操作太繁瑣,建議使用圖形界面操作,因此本文也 ...
  • sqlServer2012(936 簡體中文GBK )為例: 例如: varchar(10),只能存儲10個英文字元或數字,也只能存儲5個漢字; char(10),只能存儲10個英文字元或數字,也只能存儲5個漢字; nvarchar(10),即存儲10個英文字元或數字,也能存儲10個漢字; ncha ...
  • 知識點 △用資料庫的原因 1文件操作的複雜度 2同步 3併發處理 4安全 △資料庫系統(DBS) 資料庫(DB) + 資料庫管理系統 (DBS)+ 資料庫應用程式 + 資料庫管理員 (BDA)+ 最終用戶 △資料庫管理系統 DBM 網路應用服務端 我們要使用服務端的數據 需要有一個客戶端 客戶端可以 ...
  • 常見的SQL優化方式 1. 對查詢進行優化,應儘量避免全表掃描,首先應考慮在 where及order by 涉及的列上建立索引 。 2. 應儘量 避免 在 where 子句中對欄位進行null 值判斷,否則將導致引擎放棄使用索引而進行全表掃描,如: 可以在num上設置預設值0,確保表中num列是否存 ...
  • 前言 這是 "Android 9.0 AOSP 系列" 的第五篇了,先來回顧一下前面幾篇的大致內容。 "Java 世界的盤古和女媧 —— Zygote" 主要介紹了 Android 世界的第一個 Java 進程 的啟動過程。 註冊服務端 socket,用於響應客戶端請求 各種預載入操作,類,資源,共 ...
  • 我們編寫一個能夠用過按鈕動態更替碎片的APP,首先在主頁上顯示第一個碎片,點擊按鈕後可以替換到第二個碎片,或者刪除已經替換掉的第二個碎片。 一.MainActivity.java import androidx.fragment.app.FragmentActivity; import androi ...
  • 隨著Kotlin的推廣,一些國內公司的安卓項目開發,已經從Java完全切成Kotlin了。雖然Kotlin在各類編程語言中的排名比較靠後(據TIOBE發佈了 19 年 8 月份的編程語言排行榜,Kotlin竟然排名45位),但是作為安卓開發者,掌握該語言,卻已是大勢所趨了。 Kotlin的基礎用法, ...
一周排行
  • " 返回《C 併發編程》" "1. 概念介紹" "2. 非同步編程" "2.1. async運行過程" "2.2. async運行中同步上下文簡介" "2.3. 創建Task實例" "2.4. 捕獲非同步異常類型" "3. 並行編程" "3.1. Parallel" "3.2. 異常處理" "3.3. ...
  • 我們先看看兩個特效,感受一下,有沒有學習的動力? 核心API:Texture2D.SetPixel(int x, int y, Color color),Texture2D.Apply() 實現原理:對象池 思路: 第一幀繪製前:遍歷瓦片上所有活著的粒子對象並且進行數據操作(或運動,死亡),發生運動 ...
  • 原來的導出方式比較適用於比較簡單的導出,每一條數據在一行,數據列雖然自定義程度比較高,如果要一條數據對應多行就做不到了,於是就想支持根據模板導出,在 1.8.0 版本中引入了根據模板導出的功能 ...
  • 創建一個bat腳本, 裡面寫上: reg delete HKEY_CURRENT_USER\Software\JetBrains\dotMemory /freg delete HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Ex ...
  • Linux下有vsyscall來優化一些例如time(NULL), gettimeofday這種調用的消耗; 但是Windows下, 沒有類似的東西, 但是思路還是有的 1. 程式啟動的時候, 獲取一下準確的時間戳 2. 然後每次需要獲取時間的時候, 獲取一下流逝的時間, 可以通過獲取CPU的tic ...
  • 當用戶按下鍵盤上的一個鍵時,就會發生一系列事件。下表根據他們的發生順序列出了這些事件: 表 所有元素的鍵盤事件(按順序) 鍵盤處理永遠不會像上面看到的這麼簡單。一些控制項可能會掛起這些事件中的某些事件,從而可執行自己更特殊的鍵盤處理。最明顯的例子是TextBox控制項,它掛起了TextInput事件。對 ...
  • static void LocalMethod() { Cube(100); void Cube(int x) => Console.WriteLine($"The cube of {x} is {x * x * x}"); } static void GoToDemo() { int i = 1; ...
  • 滑鼠事件執行幾個關聯的任務。當滑鼠移到某個元素上時,可通過最基本的滑鼠事件進行響應。這些事件是MouseEnter(當滑鼠指針移到元素上時引發該事件)和MouseLeave(當滑鼠指針離開元素時引發該事件)。這兩個事件都是直接事件,這意味著他們不使用冒泡和隧道過程,而是源自一個元素並且只被該元素引發 ...
  • 反射這個詞聽起來就很牛逼是吧? 嗯的確,反射是比較高級的特性,只有語言基礎很扎實的Dev們才應該使用它。 搞點反射,可以提高程式的靈活性、可擴展性、耦合度。 反射這東西,是為了動態地運行時載入,相比於靜態代碼。編譯的時候就是板上釘釘了。 就是說,如果你的程式需要在運行時搞一些晚綁定,動態載入或檢查對 ...
  • 眾所周知,微服務架構是由一眾微服務組成,項目中調用其他微服務介面更是常見的操作。為了便於調用外部介面,我們的常用思路一般都是封裝一個外部介面的客戶端,使用時候直接調用相應的方法。webservice或WCF的做法就是引用服務,自動生成客戶端。在webapi2.0里,我們都會手動封裝一個靜態類。那麼在 ...
x