kotlin對通道理解

一、什麼是通道

延期的值提供了一種便捷的方法使單個值在多個協程之間進行相互傳輸。 通道提供了一種在流中傳輸值的方法。
Channel 是一個和 BlockingQueue 非常相似的概念。其中一個不同是它代替了阻塞的 put 操作並提供了掛起的 send,還替代了阻塞的 take 操作並提供了掛起的 receive。

二、通道的基本用法

val channel = Channel<Int>()
launch {
    // 這裏可能是消耗大量 CPU 運算的異步邏輯,我們將僅僅做 5 次整數的平方併發送
    for (x in 1..5) channel.send(x * x)
}
// 這裏我們打印了 5 次被接收的整數:
repeat(5) { println(channel.receive()) }
println("Done!")

三、關閉通道

和隊列不同,一個通道可以通過被關閉來表明沒有更多的元素將會進入通道。 在接收者中可以定期的使用 for 循環來從通道中接收元素。

從概念上來說,一個 close 操作就像向通道發送了一個特殊的關閉指令。 這個迭代停止就說明關閉指令已經被接收了。所以這裏保證所有先前發送出去的元素都在通道關閉前被接收到。

val channel = Channel<Int>()
launch {
    for (x in 1..5) channel.send(x * x)
    channel.close() // 我們結束髮送
}
// 這裏我們使用 `for` 循環來打印所有被接收到的元素(直到通道被關閉)
for (y in channel) println(y)
println("Done!")

四、構建通道生產者

協程生成一系列元素的模式很常見。 這是 生產者——消費者 模式的一部分,並且經常能在併發的代碼中看到它。 你可以將生產者抽象成一個函數,並且使通道作爲它的參數,但這與必須從函數中返回結果的常識相違悖。

這裏有一個名爲 produce 的便捷的協程構建器,可以很容易的在生產者端正確工作, 並且我們使用擴展函數 consumeEach 在消費者端替代 for 循環:

fun CoroutineScope.produceSquares(): ReceiveChannel<Int> = produce {
    for (x in 1..5) send(x * x)
}

fun main() = runBlocking {
    val squares = produceSquares()
    squares.consumeEach { println(it) }
    println("Done!")
}

五、扇出

多個協程也許會接收相同的管道,在它們之間進行分佈式工作。 讓我們啓動一個定期產生整數的生產者協程 (每秒十個數字):

fun CoroutineScope.produceNumbers() = produce<Int> {
    var x = 1 // 從 1 開始
    while (true) {
        send(x++) // 產生下一個數字
        delay(100) // 等待 0.1 秒
    }
}

接下來我們可以得到幾個處理器協程。在這個示例中,它們只是打印它們的 id 和接收到的數字:

fun CoroutineScope.launchProcessor(id: Int, channel: ReceiveChannel<Int>) = launch {
    for (msg in channel) {
        println("Processor #$id received $msg")
    }    
}

現在讓我們啓動五個處理器協程並讓它們工作將近一秒:

val producer = produceNumbers()
repeat(5) { launchProcessor(it, producer) }
delay(950)
producer.cancel() // 取消協程生產者從而將它們全部殺死

六、扇入

多個協程可以發送到同一個通道。 比如說,讓我們創建一個字符串的通道,和一個在這個通道中以指定的延遲反覆發送一個指定字符串的掛起函數:

suspend fun sendString(channel: SendChannel<String>, s: String, time: Long) {
    while (true) {
        delay(time)
        channel.send(s)
    }
}

現在,我們啓動了幾個發送字符串的協程,讓我們看看會發生什麼 (在示例中,我們在主線程的上下文中作爲主協程的子協程來啓動它們):

val channel = Channel<String>()
launch { sendString(channel, "foo", 200L) }
launch { sendString(channel, "BAR!", 500L) }
repeat(6) { // 接收前六個
    println(channel.receive())
}
coroutineContext.cancelChildren() // 取消所有子協程來讓主協程結束

七、帶緩衝的通道

到目前爲止展示的通道都是沒有緩衝區的。無緩衝的通道在發送者和接收者相遇時傳輸元素(也稱“對接”)。如果發送先被調用,則它將被掛起直到接收被調用, 如果接收先被調用,它將被掛起直到發送被調用。

Channel() 工廠函數與 produce 建造器通過一個可選的參數 capacity 來指定 緩衝區大小 。緩衝允許發送者在被掛起前發送多個元素, 就像 BlockingQueue 有指定的容量一樣,當緩衝區被佔滿的時候將會引起阻塞。

看看如下代碼的表現:

val channel = Channel<Int>(4) // 啓動帶緩衝的通道
val sender = launch { // 啓動發送者協程
    repeat(10) {
        println("Sending $it") // 在每一個元素髮送前打印它們
        channel.send(it) // 將在緩衝區被佔滿時掛起
    }
}
// 沒有接收到東西……只是等待……
delay(1000)
sender.cancel() // 取消發送者協程
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章