Kotlin 協程之Channel

前言

在之前的文章中,我們已經知道了協程的啟動、掛起、取消、異常以及常用的協程作用域等基礎應用。
這些基礎應用適合的場景是一次性任務,執行完就結束了的場景。

launch / async 適合的場景

  • 網絡請求
  • 數據庫查詢
  • 文件讀寫
  • 并行計算任務
  • 等等

Channel 適合的場景

而對于一些相對復雜的場景,例如:持續的數據流、需要在不同的協程之間傳遞數據、需要順序或背壓控制等場景,基礎的 launch / async
就不夠用了。

例如:

  • 用戶點擊、輸入等事件流的處理
  • 生產者-消費者模型的需求:任務排隊、日志流
  • 高頻數據源處理(相機幀、音頻流等)

類似這種持續的、需要順序控制、或者多個協程配合執行的場景,就需要用到 Channel 了。


Channel 的概念和基本使用

概念

顧名思義,Channel 有管道、通道的意思。Channel 跟 Java 中的 BlockingQueue 很相似,區別在于 Channel 是掛起的,不是阻塞的。

Channel 的核心特點就是能夠在不同的協程之間進行數據傳遞,并且能夠控制數據傳遞的順序。
使用起來很簡單,基本就分為以下幾步:

  1. 創建 Channel
  2. 通過 channel.send 發送數據
  3. 通過 channel.receive 接收數據

整體的概念也比較簡單形象,就是一根管道,一個口子發送數據,一個口子接收數據。

Channel 的創建

先來看下 Channel 的源碼,可以看到會根據傳入的參數選擇不同的實現。

public fun <E> Channel(capacity: Int = RENDEZVOUS,onBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND,onUndeliveredElement: ((E) -> Unit)? = null
): Channel<E> =when (capacity) {RENDEZVOUS -> {if (onBufferOverflow == BufferOverflow.SUSPEND)BufferedChannel(RENDEZVOUS, onUndeliveredElement) // an efficient implementation of rendezvous channelelseConflatedBufferedChannel(1,onBufferOverflow,onUndeliveredElement) // support buffer overflow with buffered channel}CONFLATED -> {require(onBufferOverflow == BufferOverflow.SUSPEND) {"CONFLATED capacity cannot be used with non-default onBufferOverflow"}ConflatedBufferedChannel(1, BufferOverflow.DROP_OLDEST, onUndeliveredElement)}UNLIMITED -> BufferedChannel(UNLIMITED,onUndeliveredElement) // ignores onBufferOverflow: it has buffer, but it never overflowsBUFFERED -> { // uses default capacity with SUSPENDif (onBufferOverflow == BufferOverflow.SUSPEND) BufferedChannel(CHANNEL_DEFAULT_CAPACITY,onUndeliveredElement)else ConflatedBufferedChannel(1, onBufferOverflow, onUndeliveredElement)}else -> {if (onBufferOverflow === BufferOverflow.SUSPEND) BufferedChannel(capacity, onUndeliveredElement)else ConflatedBufferedChannel(capacity, onBufferOverflow, onUndeliveredElement)}}

參數概覽

參數類型默認值描述
capacityIntRENDEZVOUS通道容量,決定緩沖區大小和行為模式
onBufferOverflowBufferOverflowSUSPEND緩沖區溢出時的處理策略
onUndeliveredElement((E) -> Unit)?null元素未能送達時的回調函數
capacity(容量配置)

capacity 參數決定了 Channel 的緩沖行為和容量大小:

  • RENDEZVOUS(值為 0):無緩沖,發送者和接收者必須同時準備好
  • CONFLATED(值為 -1):只保留最新的元素,舊元素會被覆蓋
  • UNLIMITED(值為 Int.MAX_VALUE):理論上就是無限容量,永不阻塞發送
  • BUFFERED(值為 64):默認緩沖大小
  • 自定義正整數:自己指定具體的緩沖區大小
onBufferOverflow(溢出策略)

當緩沖區滿時的處理策略:

  • SUSPEND:掛起發送操作,等待緩沖區有空間(默認)
  • DROP_OLDEST:丟棄舊的元素,添加新元素
  • DROP_LATEST:丟棄新元素,保留緩沖區中的現有元素
onUndeliveredElement(未送達回調)

當元素無法送達時的清理回調函數:

  • null:不執行任何清理操作(默認)
  • 自定義函數:用于資源清理、日志記錄等,根據業務需求來定義

參數組合效果

capacityonBufferOverflow行為適用場景
RENDEZVOUSSUSPEND無緩沖,同步通信嚴格的生產者-消費者同步
BUFFEREDSUSPEND有限緩沖,滿時掛起一般的異步處理,默認的緩沖數量是 64
UNLIMITEDSUSPEND緩沖長度為 Int.MAX_VALUE高吞吐量場景(生產上不建議使用,有內存方面的風險)
CONFLATEDDROP_OLDEST無緩沖,只保留最新值狀態更新、實時數據
自定義大小SUSPEND固定大小,滿時掛起批量處理、批量任務
自定義大小DROP_OLDEST固定大小,丟棄舊數據獲取最近 N 個元素
自定義大小DROP_LATEST固定大小,拒絕新數據保護重要歷史數據

Capacity

RENDEZVOUS(會合模式)

特點:

  • 容量為 0,無緩沖區
  • 發送者和接收者必須同時準備好才能完成數據傳輸
  • 提供強同步保證,一手交錢一手交貨

使用示例:

suspend fun demonstrateRendezvousChannel() {// 創建 RENDEZVOUS Channel(默認容量為 0),默認什么都不傳就是 rendezvous 模式,Channel<String>()val rendezvousChannel = Channel<String>(Channel.RENDEZVOUS)// 啟動發送者協程val senderJob = GlobalScope.launch {println("[發送者] 準備發送消息...")rendezvousChannel.send("Hello from RENDEZVOUS!")println("[發送者] 消息已發送")rendezvousChannel.send("Second message")println("[發送者] 第二條消息已發送")rendezvousChannel.close()}// 啟動接收者協程val receiverJob = GlobalScope.launch {delay(1000) // 延遲1秒,發送者會等待接收者準備好println("[接收者] 開始接收消息...")for (message in rendezvousChannel) {println("[接收者] 收到消息: $message")delay(500) // 模擬處理時間}println("[接收者] Channel已關閉")}// 等待所有協程完成joinAll(senderJob, receiverJob)
}

執行結果
在這里插入圖片描述

CONFLATED(只留最新值)

特點:

  • 容量為 1,但會丟棄舊值
  • 只保留最新的元素
  • 發送操作永不阻塞
  • 只能使用 BufferOverflow.SUSPEND 策略

源碼分析:

CONFLATED -> {require(onBufferOverflow == BufferOverflow.SUSPEND) {"CONFLATED capacity cannot be used with non-default onBufferOverflow"}ConflatedBufferedChannel(1, BufferOverflow.DROP_OLDEST, onUndeliveredElement)
}

使用示例:

suspend fun demonstrateConflatedChannel() {// 創建 CONFLATED Channel,相當于:Channel<String>(1, BufferOverflow.DROP_OLDEST)val conflatedChannel = Channel<String>(Channel.CONFLATED)// 快速發送多個消息val senderJob = GlobalScope.launch {repeat(5) { i ->val message = "Update-$i"conflatedChannel.send(message)println("[發送者] 發送更新: $message")delay(100) // 短暫延遲}conflatedChannel.close()}// 慢速接收者val receiverJob = GlobalScope.launch {delay(1000) // 延遲1秒,讓發送者發送完所有消息println("[接收者] 開始接收(只會收到最新的值)...")for (message in conflatedChannel) {println("[接收者] 收到: $message")}}joinAll(senderJob, receiverJob)
}

在這里插入圖片描述

UNLIMITED(無限容量)

特點:

  • 容量為 Int.MAX_VALUE,理論上無限容量
  • 發送操作永不阻塞,但要注意內存使用
  • 忽略 onBufferOverflow 參數
  • 適用于高吞吐量場景,但生產環境需謹慎使用
suspend fun demonstrateUnlimitedChannel() {val unlimitedChannel = Channel<String>(Channel.UNLIMITED)val senderJob = GlobalScope.launch {repeat(10) { i ->val message = "Message-$i"unlimitedChannel.send(message)println("[發送者] 立即發送: $message")}unlimitedChannel.close()println("[發送者] 所有消息已發送,Channel已關閉")}val receiverJob = GlobalScope.launch {delay(1000) // 延遲1秒開始接收println("[接收者] 開始慢速接收...")for (message in unlimitedChannel) {println("[接收者] 處理: $message")delay(300) // 模擬處理時間}}joinAll(senderJob, receiverJob)
}

在這里插入圖片描述

BUFFERED(有限容量)

特點:

  • 使用默認容量 (CHANNEL_DEFAULT_CAPACITY,通常為 64)
  • 在緩沖區滿時根據 onBufferOverflow 策略處理

源碼分析:

BUFFERED -> {if (onBufferOverflow == BufferOverflow.SUSPEND)BufferedChannel(CHANNEL_DEFAULT_CAPACITY, onUndeliveredElement)elseConflatedBufferedChannel(1, onBufferOverflow, onUndeliveredElement)
}

使用示例:

suspend fun demonstrateBufferedDefaultChannel() {// 創建 BUFFERED Channel(默認容量為 64)val bufferedChannel = Channel<String>(Channel.BUFFERED)val senderJob = GlobalScope.launch {repeat(100) { i ->bufferedChannel.send("Message-$i")println("[發送者] 發送 Message-$i")}bufferedChannel.close()}val receiverJob = GlobalScope.launch {delay(1000) // 延遲接收for (message in bufferedChannel) {println("[接收者] 收到: $message")delay(50)}}joinAll(senderJob, receiverJob)
}

與下面自定義容量效果類似。

自定義容量

特點:

  • 指定具體的緩沖區大小
  • 根據 onBufferOverflow 策略處理溢出

源碼分析:

else -> {if (onBufferOverflow === BufferOverflow.SUSPEND)BufferedChannel(capacity, onUndeliveredElement)elseConflatedBufferedChannel(capacity, onBufferOverflow, onUndeliveredElement)
}

使用示例:

suspend fun demonstrateBufferedChannel() {// 創建容量為3的緩沖Channelval bufferedChannel = Channel<Int>(capacity = 3)// 啟動發送者協程val senderJob = GlobalScope.launch {repeat(5) { i ->println("[發送者] 發送數字: $i")bufferedChannel.send(i)println("[發送者] 數字 $i 已發送")}bufferedChannel.close()println("[發送者] Channel已關閉")}// 啟動接收者協程,延遲接收以觀察緩沖效果val receiverJob = GlobalScope.launch {delay(2000) // 延遲2秒開始接收println("[接收者] 開始接收數字...")for (number in bufferedChannel) {println("[接收者] 收到數字: $number")delay(800) // 模擬慢速處理}}joinAll(senderJob, receiverJob)
}

可以看到,因為默認的溢出策略是 SUSPEND,所以當緩沖區滿了時,發送者會被掛起,直到接收者處理完一個元素,才會繼續發送。
在這里插入圖片描述


BufferOverflow 策略詳解

當 Channel 的緩沖區滿時,BufferOverflow 參數決定了如何處理新的發送請求:

SUSPEND(默認策略)

  • 行為:當緩沖區滿時,掛起發送操作直到有空間可用
  • 特點:提供背壓控制,防止生產者過快
  • 使用場景:需要確保所有數據都被處理的場景
suspend fun demonstrateBasicOperations() {//容量為 2,溢出策略為SUSPENDval channel = Channel<String>(capacity = 2, onBufferOverflow = BufferOverflow.SUSPEND)//發送的速度快val job1 = GlobalScope.launch {repeat(5) {channel.send("Message-$it")println("[發送者] 發送 Message-$it")}channel.close()}val job2 = GlobalScope.launch {//除了用 channel.recrive 外,也可以直接 用 for 循環接收數據for (message in channel) {//接收的速度慢delay(1000)println("[接收者] 接收到: $message")}}joinAll(job1, job2)
}

在這里插入圖片描述

DROP_LATEST

  • 行為:當緩沖區滿時,丟棄新元素,保留緩沖區中的現有元素
  • 特點:保護已緩沖的數據不被覆蓋
  • 使用場景:保護重要的歷史數據,防止新數據覆蓋
  • 性能特點:發送操作永不阻塞,但新數據可能被丟棄
suspend fun demonstrateBasicOperations() {val channel = Channel<String>(capacity = 2, onBufferOverflow = BufferOverflow.DROP_LATEST)val job1 = GlobalScope.launch {repeat(5) {channel.send("Message-$it")println("[發送者] 發送 Message-$it")}channel.close()}val job2 = GlobalScope.launch {for (message in channel) {delay(1000)println("[接收者] 接收到: $message")}}joinAll(job1, job2)
}

可以看到,當緩沖區滿時,會把新數據丟棄掉,因此,接收端只接收到了舊數據。

在這里插入圖片描述

DROP_OLDEST

  • 行為:當緩沖區滿時,丟棄舊的元素,添加新元素
  • 特點:保持固定的內存使用,優先保留新數據
  • 使用場景:實時數據流、最近N個元素
  • 性能特點:發送操作永不阻塞,但可能丟失歷史數據
suspend fun demonstrateBasicOperations() {val channel = Channel<String>(capacity = 2, onBufferOverflow = BufferOverflow.DROP_OLDEST)val job1 = GlobalScope.launch {repeat(5) {channel.send("Message-$it")println("[發送者] 發送 Message-$it")}channel.close()}val job2 = GlobalScope.launch {for (message in channel) {delay(1000)println("[接收者] 接收到: $message")}}joinAll(job1, job2)
}

在這里插入圖片描述

需要注意的是,當緩沖區滿了之后,1 和 2 被丟棄了,3 和 4 被放進去了。從這里可以看出,丟棄數據時,并不是把最早的舊數據丟掉,這里跟內部的實現有關。


onUndeliveredElement 回調

當元素無法送達時(如 Channel 被取消或關閉),會調用此回調函數

suspend fun demonstrateBasicOperations() {val channel = Channel<String>(capacity = 2, onBufferOverflow = BufferOverflow.DROP_OLDEST) {println("[Channel] 緩沖區已滿,無法放到緩沖區,值:${it}")}// 演示基本的send和receive操作val job1 = GlobalScope.launch {repeat(5) {channel.send("Message-$it")println("[發送者] 發送 Message-$it")}channel.close()}val job2 = GlobalScope.launch {for (message in channel) {delay(1000)println("[接收者] 接收到: $message")}}joinAll(job1, job2)
}

在這里插入圖片描述


Channel 操作方式

Channel 提供了兩種操作方式:阻塞操作和非阻塞操作。

阻塞操作(send/receive)

send()receive() 方法都是掛起方法,它們會阻塞當前協程,直到完成操作。

非阻塞操作(trySend/tryReceive)

trySend()tryReceive() 是 Channel 提供的非阻塞操作 API。與阻塞版本不同,這些方法會立即返回結果,不會掛起當前協程,也不會拋出異常。

操作對比

操作類型阻塞版本非阻塞版本行為差異
發送send()trySend()send() 會掛起直到有空間;trySend() 立即返回結果
接收receive()tryReceive()receive() 會掛起直到有數據;tryReceive() 立即返回結果

返回值類型

  • trySend() 返回 ChannelResult<Unit>
  • tryReceive() 返回 ChannelResult<T>

ChannelResult 是一個密封類,通過密封類中的成員 isSuccessgetOrNull() 可以判斷操作是否成功。

在這里插入圖片描述

大部分場景下,send / receive + 合理的 Channel 配置就能解決問題,trySend/tryReceive 更多的是想達到如下效果:

  • 避免不必要的協程掛起開銷,希望立即得到結果
  • 提供更精細的控制邏輯,如:超時處理、重試機制等
  • 實現更好的錯誤處理和用戶反饋,能更好地處理異常場景

runBlocking {val channel = Channel<Int>(2)val sendJob = launch {repeat(5) {delay(100)val sendResult = channel.trySend(it)sendResult.onSuccess {println("發送成功")}.onFailure {println("發送失敗")}.onClosed {println("通道已關閉")}}}val receiveJob = launch {for (i in channel) {delay(300)println("接收到數據:${i}")}}joinAll(sendJob, receiveJob)}

在這里插入圖片描述

Channel 狀態管理

Channel 在其生命周期中會經歷以下幾個關鍵狀態:

  • 活躍狀態(Active):可以正常發送和接收數據
  • 發送端關閉(Closed for Send):不能發送新數據,但可以接收緩沖區中的數據
  • 接收端關閉(Closed for Receive):不能接收數據,緩沖區已清空
  • 取消狀態(Cancelled):Channel 被取消,所有操作都會失敗

API

  • channel.close():關閉 Channel
  • channel.isClosedForSend:判斷發送端是否已關閉
  • channel.isClosedForReceive:判斷接收端是否已關閉
  • channel.cancel():取消 Channel

Close(關閉操作)

  • 調用 close() 后,isClosedForSend 立即變為 true
  • 此時,緩沖區中的數據仍可被消費
  • 只有當緩沖區清空后,isClosedForReceive 才變為 true

示例:

    suspend fun demonstrateChannelClose() {val channel = Channel<String>(1)val producer = GlobalScope.launch {try {for (i in 1..5) {val message = "Message $i"println("準備發送: $message")channel.send(message)println("成功發送: $message")delay(100)}} catch (e: ClosedSendChannelException) {println("生產者: Channel已關閉,無法發送數據 - ${e.message}")}}val consumer = GlobalScope.launch {try {for (message in channel) {println("接收到: $message")delay(200)}println("消費者: Channel已關閉,退出接收循環")} catch (e: Exception) {println("消費者異常: ${e.message}")}}delay(300) // 模擬讓一些數據能夠被接收到// 檢查Channel狀態println("關閉前狀態:")println("  isClosedForSend: ${channel.isClosedForSend}")println("  isClosedForReceive: ${channel.isClosedForReceive}")// 關閉Channelprintln("\n正在關閉Channel...")channel.close()// 檢查關閉后的狀態println("關閉后狀態:")println("  isClosedForSend: ${channel.isClosedForSend}")println("  isClosedForReceive: ${channel.isClosedForReceive}")// 等待協程完成producer.join()consumer.join()println("最終狀態:")println("  isClosedForSend: ${channel.isClosedForSend}")println("  isClosedForReceive: ${channel.isClosedForReceive}")
}

在這里插入圖片描述

Cancel(取消操作)

cancel() 方法用于強制取消 Channel,它會:

  • 立即關閉發送和接收端
  • 清空緩沖區中的所有數據
  • 觸發 onUndeliveredElement 回調(如果設置了)
suspend fun demonstrateChannelCancel() {val channel = Channel<String>(capacity = 5) {println("消息未被接收:${it}")}val producer = GlobalScope.launch {try {for (i in 1..8) {val message = "Message $i"println("嘗試發送: $message")channel.send(message)println("成功發送: $message")delay(100)}} catch (e: CancellationException) {println("生產者: Channel被取消 - ${e.message}")}}val consumer = GlobalScope.launch {try {for (message in channel) {println("接收到: $message")delay(300)}} catch (e: CancellationException) {println("消費者: 協程被取消 - ${e.message}")}}delay(400) // 讓一些操作執行println("\n取消前狀態:")println("  isClosedForSend: ${channel.isClosedForSend}")println("  isClosedForReceive: ${channel.isClosedForReceive}")// 取消Channelprintln("\n正在取消Channel...")channel.cancel(CancellationException("主動取消Channel"))println("取消后狀態:")println("  isClosedForSend: ${channel.isClosedForSend}")println("  isClosedForReceive: ${channel.isClosedForReceive}")// 等待協程完成producer.join()consumer.join()
}

在這里插入圖片描述


Channel 異常處理

在使用 Channel 的過程中,會遇到各種異常情況。主要包括以下幾種類型:

ClosedSendChannelException

觸發條件:

  • 在已關閉的 Channel 上調用 send() 方法
  • Channel 調用 close() 后,發送端立即關閉

示例:

suspend fun demonstrateClosedSendException() {val channel = Channel<String>()// 關閉 Channelchannel.close()try {// 嘗試在已關閉的 Channel 上發送數據channel.send("This will throw exception")} catch (e: ClosedSendChannelException) {println("捕獲異常: ${e.message}")println("異常類型: ${e::class.simpleName}")}
}

ClosedReceiveChannelException

觸發條件:

  • 從已關閉且緩沖區為空的 Channel 調用 receive() 方法
  • isClosedForReceivetrue 時調用 receive()

示例:

suspend fun demonstrateClosedReceiveException() {val channel = Channel<String>()// 關閉 Channelchannel.close()try {// 嘗試從已關閉且空的 Channel 接收數據val message = channel.receive()println("收到消息: $message")} catch (e: ClosedReceiveChannelException) {println("捕獲異常: ${e.message}")println("異常類型: ${e::class.simpleName}")}
}

CancellationException

觸發條件:

  • Channel 被 cancel() 方法取消
  • 父協程被取消,導致 Channel 操作被取消
  • 超時或其他取消信號

示例:

suspend fun demonstrateCancellationException() {val channel = Channel<String>()val job = GlobalScope.launch {try {// 這個操作會被取消channel.send("This will be cancelled")} catch (e: CancellationException) {println("發送操作被取消: ${e.message}")throw e // 重新拋出 CancellationException}}delay(100)// 取消 Channelchannel.cancel(CancellationException("手動取消 Channel"))try {job.join()} catch (e: CancellationException) {println("協程被取消: ${e.message}")}
}

異常與狀態關系

Channel 狀態send() 行為receive() 行為trySend() 行為tryReceive() 行為
活躍狀態正常發送或掛起正常接收或掛起返回成功/失敗結果返回成功/失敗結果
發送端關閉拋出 ClosedSendChannelException正常接收緩沖區數據返回失敗結果正常返回結果
接收端關閉拋出 ClosedSendChannelException拋出 ClosedReceiveChannelException返回失敗結果返回失敗結果
已取消拋出 CancellationException拋出 CancellationException返回失敗結果返回失敗結果

異常處理技巧

使用非阻塞操作避免異常

非阻塞操作不會拋出異常,而是返回結果對象:

suspend fun safeChannelOperations() {val channel = Channel<String>()// 安全的發送操作val sendResult = channel.trySend("Safe message")when {sendResult.isSuccess -> println("發送成功")sendResult.isFailure -> println("發送失敗: ${sendResult.exceptionOrNull()}")sendResult.isClosed -> println("Channel 已關閉")}// 安全的接收操作val receiveResult = channel.tryReceive()when {receiveResult.isSuccess -> println("接收到: ${receiveResult.getOrNull()}")receiveResult.isFailure -> println("接收失敗: ${receiveResult.exceptionOrNull()}")receiveResult.isClosed -> println("Channel 已關閉")}
}
健壯的異常處理
suspend fun robustChannelUsage() {val channel = Channel<String>()val producer = GlobalScope.launch {try {repeat(5) { i ->if (channel.isClosedForSend) {println("Channel 已關閉,停止發送")break}channel.send("Message $i")delay(100)}} catch (e: ClosedSendChannelException) {println("生產者: Channel 已關閉")} catch (e: CancellationException) {println("生產者: 操作被取消")throw e // 重新拋出取消異常} finally {println("生產者: 清理資源")}}val consumer = GlobalScope.launch {try {while (!channel.isClosedForReceive) {try {val message = channel.receive()println("消費者: 收到 $message")} catch (e: ClosedReceiveChannelException) {println("消費者: Channel 已關閉且無更多數據")break}delay(200)}} catch (e: CancellationException) {println("消費者: 操作被取消")throw e} finally {println("消費者: 清理資源")}}delay(1000)channel.close()joinAll(producer, consumer)
}

總結

Channel 關鍵概念對比

特性RENDEZVOUSCONFLATEDBUFFEREDUNLIMITED自定義容量
容量0164Int.MAX_VALUE指定值
緩沖行為無緩沖,同步只保留最新值有限緩沖無限緩沖有限緩沖
發送阻塞緩沖滿時緩沖滿時
適用場景嚴格同步狀態更新一般異步高吞吐量批量處理
內存風險中等可控

溢出策略對比

策略行為性能特點適用場景
SUSPEND掛起發送操作提供背壓控制確保數據完整性
DROP_OLDEST丟棄舊元素發送不阻塞實時數據流
DROP_LATEST丟棄新元素發送不阻塞保護歷史數據

操作方式

操作類型阻塞版本非阻塞版本異常處理返回值
發送send()trySend()拋出異常ChannelResult<Unit>
接收receive()tryReceive()拋出異常ChannelResult<T>
特點會掛起協程立即返回需要 try-catch通過結果對象判斷

Channel 狀態生命周期

狀態描述send()receive()檢查方法
活躍正常工作狀態? 正常? 正常-
發送關閉調用 close() 后? 異常? 可接收緩沖區數據isClosedForSend
接收關閉緩沖區清空后? 異常? 異常isClosedForReceive
已取消調用 cancel() 后? 異常? 異常-

總體來說,Channel 是一種非常強大的協程通信機制,它可以幫助我們在協程之間進行安全、高效的通信。在使用 Channel時,我們需要注意異常處理、緩沖區容量、溢出策略等問題。


感謝閱讀,如果對你有幫助請三連(點贊、收藏、加關注)支持。有任何疑問或建議,歡迎在評論區留言討論。如需轉載,請注明出處:喻志強的博客

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/919379.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/919379.shtml
英文地址,請注明出處:http://en.pswp.cn/news/919379.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

linux系統裝google chrome,amd64

google chrome官網最下邊其他平臺&#xff0c;linux 查看自己的系統架構&#xff08;用下邊這行代碼查看&#xff09;&#xff0c;看看是amd還是 &#xff0c;我的顯示amd64&#xff0c;amd對應.deb,rpm對應x86 &#xff0c;選擇下載 dpkg --print-architecture 然后 sudo…

【C++基礎】C++ 中const與volatile關鍵字深度解析:從面試考點到底層實現

在 C 開發崗位的面試中&#xff0c;const與volatile關鍵字是高頻考點之一。這兩個關鍵字看似簡單&#xff0c;但實際上蘊含著豐富的語義和底層機制。本文從基礎語法到高級應用&#xff0c;結合大廠真題&#xff0c;深入解析這兩個關鍵字的奧秘。一、const關鍵字&#xff1a;常量…

達夢分布式集群DPC_故障分析_yxy

達夢分布式集群DPC_節點故障分析1 DPC核心概念回顧2 場景1-主庫故障3 場景2-少數備庫故障4 場景3-多數節點故障4.1 多數節點故障&#xff08;包括主庫&#xff09;4.2 多數備庫節點故障&#xff08;不包括主庫&#xff09;1 DPC核心概念回顧 達夢分布式集群DPC&#xff0c;基于…

【高并發內存池】一、簡介 定長內存池實現

文章目錄Ⅰ. 項目介紹1、這個項目要做什么2、項目的要求Ⅱ. 什么是內存池1、池化技術2、內存池3、mallocⅢ. 設計一個定長內存池1、定長內存池的概念2、實現如何實現定長???如何繞開 malloc 向堆直接申請空間???3、性能測試Ⅰ. 項目介紹 1、這個項目要做什么 tcmalloc源…

產品設計.原型設計

產品思維&#xff1a; 1. 產品定位&#xff1a;產品的具體的、用戶畫像&#xff1b; --什么樣的人在什么環境下做什么事情的場景 2. 范圍層: 發現、識別和決策需求。--識別真假需求&#xff0c;做ROI判斷 3. 可復用的、MVP產品方案--要能復用的解決方案&#xff0c;最小可用產品…

vue3+element-plus 輸入框el-input設置背景顏色和字體顏色,樣式效果等同于不可編輯的效果

應用效果&#xff1a;代碼&#xff1a;<template> ......<el-form-item label"文件編號" label-position"right"><el-input v-model"qualityFileForm.fileNo" clearable :disabled"!props.isNew" /></el-form-it…

[ CSS 前端 ] 網頁內容的修飾

目錄 一. CSS 1. 概述 2. 基本語法 (1)行內樣式表 (2)內嵌樣式表 (3)外部樣式表 3. 選擇器 (1)標簽選擇器: (2)類選擇器: (3)通配選擇器: (4)后代選擇器: 4. 基礎樣式 (1). 文本樣式 (2). 背景樣式 (3). 列表樣式 5. 偽類 (1)定義: (2)偽類的語法&#xff1a; …

全面深入了解榛樹游戲引擎

本文還有配套的精品資源&#xff0c;點擊獲取 簡介&#xff1a;榛樹游戲引擎&#xff08;Hazel&#xff09;是一款專為游戲開發設計的先進軟件工具&#xff0c;它集成了多種功能&#xff0c;支持現代圖形API&#xff0c;具有高性能的物理模擬系統和易學易用的腳本語言&#…

“大模型”技術專欄 | 淺談基于 Kubernetes 的 LLM 分布式推理框架架構:概覽

編者按&#xff1a;人工智能正以前所未有的滲透力重塑生產與生活圖景。作為國內領先的數據智能科技企業&#xff0c;和鯨科技自 2015 年成立以來&#xff0c;深耕人工智能與數據科學&#xff0c;歷經十年發展&#xff0c;已在氣象、教育、醫療、航空航天、金融、通信、能源、零…

【JS】認識并實現一個chrome擴展程序

&#x1f60f;★,:.☆(&#xffe3;▽&#xffe3;)/$:.★ &#x1f60f; 這篇文章主要介紹chrome擴展程序。 學其所用&#xff0c;用其所學。——梁啟超 歡迎來到我的博客&#xff0c;一起學習&#xff0c;共同進步。 喜歡的朋友可以關注一下&#xff0c;下次更新不迷路&#…

jeecgboot項目遇見的一些問題:

1.當你想修改項目的標題&#xff0c;前端將jeecgboot改成你想要的標題的時候&#xff0c;去前端的.env文件中進行修改。圖1 修改標題根據路徑找到文件&#xff0c;將網站標題改成自己需要的就可以正常顯示了。圖2 顯示前圖3 顯示后2.在動態數組中&#xff0c;如果你知道數組需要…

項目里程碑設定有哪些方法

要科學設定項目里程碑&#xff0c;可采用以下幾種方法&#xff1a;基于項目階段劃分法、關鍵交付物導向法、依賴關系鏈分析法、時間驅動法、風險節點識別法、目標成果導向法、資源約束分析法、客戶驗收節點設定法。其中&#xff0c;關鍵交付物導向法尤為實用。該方法以項目中必…

英偉達顯卡驅動怎么更新 詳細步驟教程

英偉達顯卡驅動程序對于電腦的圖形性能至關重要&#xff0c;它能確保顯卡在游戲、設計、視頻渲染等方面發揮最大性能。如果驅動過舊&#xff0c;可能會導致游戲運行不暢、軟件不兼容&#xff0c;甚至系統出現錯誤。因此&#xff0c;定期更新英偉達顯卡驅動非常必要。下面將為大…

基于單片機智能拐杖/導盲杖/老人防摔倒設計

傳送門 &#x1f449;&#x1f449;&#x1f449;&#x1f449;其他作品題目速選一覽表 &#x1f449;&#x1f449;&#x1f449;&#x1f449;其他作品題目功能速覽 概述 該設計針對老年人及行動不便人群的需求&#xff0c;開發了一款集成單片機控制的智能拐杖。拐杖采…

Node.js完整安裝配置指南(包含國內鏡像配置)

Node.js完整安裝配置指南&#xff08;包含國內鏡像配置&#xff09; 一、Node.js安裝 方法1&#xff1a;使用Chocolatey安裝&#xff08;推薦&#xff09; # 安裝最新LTS版本 choco install nodejs# 或安裝指定版本 choco install nodejs --version20.11.0方法2&#xff1a;…

AI硬件 - AMD顯卡架構演進及產品線

目錄 一、AMD顯卡架構演進總結 二、典型AMD AI顯卡歷代型號參數對比表 關鍵參數說明: 三、AMD 特供中國AI顯卡產品線全覽 1. 企業級Instinct系列(數據中心/科研) 2. 消費級AI加速顯卡(開發/本地推理) 四、與NVIDIA顯卡的AI性能對比 關鍵指標實測數據 五、模型框架…

論文閱讀-Gated CRF Loss for Weakly Supervised Semantic Image Segmentation

文章目錄1 背景2 模塊2.1 部分交叉熵損失2.2 弱標簽&#xff08;線/點&#xff09;2.3 Gated CRF Loss3 效果3.1 總體效果3.2 消融實驗4 總結參考文獻1 背景 全監督的語義分割需要對全圖進行完全而精確的標注。當需要標注的目標在圖像中較多&#xff0c;又或形狀不規則&#x…

零墨云A4mini打印機設置電腦通過局域網絡進行打印

文檔時間&#xff1a;2025年8月 1.演示環境 操作系統版本&#xff1a;Windows11 打印機版本&#xff1a;零墨云A4mini 這款打印機打印的方式有藍牙、遠程云和局域網&#xff0c;這里演示的是電腦通過局域網打印 通過電腦版局域網(這個局域網是網絡可達)打印之前&#xff0c…

ESP8266 入門(第 3 部分):使用 Arduino IDE 對 ESP8266 進行編程并刷新其內存

使用 Arduino IDE 對 ESP8266 進行編程并刷新其內存 這是我們之前 ESP 教程的延續的第三個教程,其中我們將學習使用 Arduino IDE(不使用 Arduino)對 ESP8266 進行編程和燒錄 ESP8266。在前面的教程中,我們介紹了 WiFi 收發器ESP8266簡介以及將 AT 命令與 ESP8266 結合使用。…

如何成功初始化一個模塊

一、如何保證成功初始化一個模塊&#xff08;以 UART 為例&#xff09;要成功初始化一個模塊&#xff0c;請遵循以下步驟&#xff1a;在圖形化界面中&#xff0c;首先配置外設模塊。緊接著&#xff0c;配置使用到的外設模塊的引腳&#xff08;這一點很重要&#xff0c;容易忘記…