Kotlin 協程之Channel的概念和基本使用

前言

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

launch / async 適合的場景

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

而對于一些相對復雜的場景,例如:持續的數據流、需要在不同的協程之間傳遞數據、需要順序或背壓控制等場景,基礎的 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/bicheng/94106.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/94106.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/94106.shtml

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

相關文章

安裝使用Conda

文章目錄Linux安裝 Conda&#xff08;Miniconda 或 Anaconda&#xff09;?Miniconda (輕量版)激活配置Windows安裝配置 Conda?添加清華鏡像源加速創建并激活 Conda 環境驗證步驟?安裝項目依賴運行項目Linux安裝 Conda&#xff08;Miniconda 或 Anaconda&#xff09;? Mini…

面向智能空戰的深度強化學習技術綜述

CSDN大禮包《大模型課程》 CSDN大禮包《深度強化學習課程》 CSDN大禮包《人工智能平臺設計開發課程》

DeepSeek-V3.1 Claude Code: 革命性的AI編碼助手詳解與應用指南

DeepSeek-V3.1 & Claude Code: 革命性的AI編碼助手詳解與應用指南 今天 DeepSeek模型已支持接入 Claude Code&#xff0c;我們來深入探討Claude Code ——Anthropic 推出的一個強大工具。它不僅僅是一個簡單的代碼補全助手&#xff0c;而是一個嵌入終端的智能代理&#xf…

智能求職推薦系統

智能求職推薦系統 基于知識圖譜和大語言模型的智能求職推薦系統&#xff0c;為求職者提供個性化崗位推薦和AI驅動的匹配分析。 &#x1f680; 系統特性 智能推薦: 基于知識圖譜的多維度職位匹配AI分析: 集成DeepSeek大模型提供深度分析和建議可視化展示: 使用ECharts展示推薦結…

瑞芯微:AIoT芯片領航者的全棧突圍與生態崛起

一、業績高速增長的底層邏輯??瑞芯微的持續爆發式增長源于三大關鍵支柱&#xff1a;技術縱深??&#xff1a;深耕“大音頻、大視頻、大感知、大軟件”四大核心技術矩陣&#xff0c;自研NPU/ISP/編解碼等核心IP持續迭代&#xff1b;精準定位??&#xff1a;瞄準邊緣側與端側…

【報錯】Please do not run this script with sudo bash

目錄 報錯 分析 解決方法 擴展 報錯分析 安裝conda 報錯 Please do not run this script with sudo bash: showHelp: command not found 分析 腳本有以下要求: 不能以root身份運行(當前是root用戶)

多線程—飛機大戰排行榜功能(2.0版本)

&#xff08;一&#xff09;實現功能&#xff1a; 1.基礎的成績排序 2.相同成績隨機排名 3.用戶名注冊重復 &#xff08;二&#xff09;效果視頻&#xff1a; &#xff08;三&#xff09;代碼實現&#xff1a; 3.1 && 3.2 在FileRead類中新增方法如下:具體的代碼實現&a…

React + Antd+TS 動態表單容器組件技術解析與實現

概述在現代前端應用中&#xff0c;表單是用戶交互的核心部分。本文將深入分析一個基于 React 和 Ant Design 的高級動態表單容器組件&#xff0c;它提供了強大的可配置性、靈活的布局選項和豐富的功能擴展能力。組件核心特性1. 高度可配置的表單結構interface FormContainerPro…

51c自動駕駛~合集16

自己的原文哦~ https://blog.51cto.com/whaosoft/11739891 #CLIP系列模型如何補短板再升級 CLIP&#xff08;Contrastive Language–Image Pre-training&#xff09;模型自推出以來&#xff0c;在圖像-文本跨模態理解和生成領域取得了顯著成果。然而&#xff0c;經…

分級設色地圖/標注式統計地圖-中國地圖繪制

分級設色地圖/標注式統計地圖?1. 這種圖長什么樣&#xff1f;?2. 核心應用場景?3. 工具3.1 自己找數據3.2 智圖小易司3.2 Flourish3.3 鏑數圖表注意事項當你看到一張中國地圖&#xff0c;各省份顏色深淺不一&#xff0c;旁邊還標注著具體數值時&#xff0c;這種圖就是?分級…

2025最新華為云國際版注冊圖文流程-不用綁定海外信用卡注冊

說到華為云&#xff0c;很多人第一反應就是“大廠可靠、服務全”。確實&#xff0c;作為全球知名的云計算服務商&#xff0c;華為云在企業級項目和個人開發者中都挺受歡迎。今天我就帶你一步一步走一遍華為云國際版的注冊流程&#xff0c;讓新手也能輕松上手。下面是最簡單的注…

Android 人臉識別技術全解析

人臉識別作為生物識別技術的核心分支&#xff0c;已廣泛應用于考勤打卡、身份驗證、支付安全等場景。在 Android 平臺&#xff0c;實現人臉識別需要兼顧準確性、實時性和設備兼容性三大挑戰。本文將系統講解 Android 人臉識別的技術選型、核心實現、性能優化及安全加固&#xf…

STM32項目分享:基于STM32單片機駕駛安全監測系統設計

“我們不做一錘子買賣&#xff0c;只做技術成長的長期伙伴&#xff01;” 目錄 一、視頻展示 二、項目簡介 三、原理圖設計 四、PCB硬件設計 五、程序設計 六、資料分享 一、視頻展示 基于stm32單片機駕駛行為監測系統設計 -視頻分享二、項目簡介 題目&#xff1a;基于s…

【GaussDB】使用gdb定位GaussDB編譯package報錯

【GaussDB】使用gdb定位GaussDB編譯package報錯 背景 在某次遷移Oracle到GaussDB時&#xff0c;應用開發人員將改好的package在GaussDB里進行創建&#xff0c;沒有ERROR也沒有WARNING&#xff0c;但是編譯無效對象的時候報錯了。雖然已經找到了是哪個包編譯報錯&#xff0c;但…

One Commander:強大的Windows文件管理器

在日常使用電腦的過程中&#xff0c;文件管理和瀏覽是必不可少的任務。One Commander作為一款功能強大的Windows文件管理器&#xff0c;提供了豐富的功能和便捷的操作方式&#xff0c;幫助用戶更高效地管理和瀏覽文件。它不僅支持多種文件操作&#xff0c;還提供了豐富的自定義…

SPUpDate Application 程序卸載

我安裝了 EzvizStudioSetups.exe 軟件&#xff0c;卸載后會在電腦遺留 SPUpDate Application 程序&#xff1b;在某一時刻會占用 CPU 資源&#xff1b;應用卸載方法一&#xff1a;在任務管理器搜索 SPUpDate Application&#xff1b;定位到文件位置&#xff1b;我的路徑如下C:\…

算法題(187):程序自動分析

審題&#xff1a; 本題需要我們判斷是否可以同時滿足題目給定的若干等式或不等式&#xff0c;判斷出后根據結果輸出YES或NO 思路&#xff1a; 方法一&#xff1a;離散化并查集 使用并查集&#xff1a;其實題目中只存在兩者相等或不等兩種情況&#xff0c;而等于具有傳遞性&…

strcasecmp函數詳解

strcasecmp 是 C 語言中用于不區分大小寫比較兩個字符串的函數&#xff0c;主要用于忽略字符大小寫差異的場景&#xff08;如用戶輸入驗證、不區分大小寫的字符串匹配等&#xff09;。它屬于 POSIX 標準庫&#xff0c;定義在 <string.h> 頭文件中。 一、函數原型與參數 函…

Voronoi圖

本文將詳細解釋 Voronoi 圖&#xff0c;它在空間分析和插值中非常常用。1. 概念 Voronoi 圖是一種空間劃分方法&#xff0c;它把平面&#xff08;或空間&#xff09;劃分成若干個區域&#xff0c;使得每個區域內的任意一點都比該區域外的任何一點更靠近該區域的“生成點”&…

BioScientist Agent:用于藥物重定位和作用機制解析的知識圖譜增強型 LLM 生物醫學代理技術報告

BioScientist Agent:用于藥物重定位和作用機制解析的知識圖譜增強型 LLM 生物醫學代理技術報告 一、項目概述 藥物研發是一個周期長、成本高的過程,平均需要超過 10 年時間和 20 億美元才能將一種新藥推向市場,且 90% 以上的候選藥物最終失敗(1)。這種低成功率主要歸因于對…