确保通道操作并发安全及错误处理的方法
- 使用select语句:
select
语句用于在多个通信操作(如通道发送和接收)之间进行多路复用。它会阻塞直到其中一个通信操作可以继续执行,这有助于避免死锁。
- 避免空的select:空的
select
语句会导致死锁,所以每个select
语句至少要有一个可以执行的分支。
- 使用互斥锁(可选):如果需要对共享资源(如在通道操作前后需要访问的一些状态变量)进行操作,可以使用互斥锁来防止数据竞争。但在大多数通道操作场景下,通道本身的特性已经可以保证并发安全,互斥锁通常用于更复杂的共享资源场景。
- 处理通道关闭:在从通道接收数据时,使用带第二个返回值的接收操作来检测通道是否关闭,避免在关闭的通道上进行无效操作。
代码示例
package main
import (
"fmt"
"sync"
)
func worker(id int, dataCh chan int, resultCh chan int, wg *sync.WaitGroup) {
defer wg.Done()
for {
data, ok := <-dataCh
if!ok {
// 通道关闭,退出循环
return
}
// 模拟一些工作
result := data * 2
select {
case resultCh <- result:
default:
fmt.Printf("Worker %d: result channel is full, data dropped\n", id)
}
}
}
func main() {
const numWorkers = 3
dataCh := make(chan int, 10)
resultCh := make(chan int, 10)
var wg sync.WaitGroup
for i := 0; i < numWorkers; i++ {
wg.Add(1)
go worker(i, dataCh, resultCh, &wg)
}
// 发送数据到dataCh
for i := 0; i < 20; i++ {
select {
case dataCh <- i:
default:
fmt.Printf("Main: data channel is full, data %d dropped\n", i)
}
}
close(dataCh)
// 等待所有worker完成
go func() {
wg.Wait()
close(resultCh)
}()
// 接收并打印结果
for result := range resultCh {
fmt.Printf("Received result: %d\n", result)
}
}
代码逻辑解释
- worker函数:
- 每个
worker
协程从dataCh
通道接收数据。使用ok
变量检测通道是否关闭,如果关闭则退出循环。
- 对接收的数据进行处理(这里简单地将数据乘以2)。
- 使用
select
语句尝试将结果发送到resultCh
通道。如果resultCh
已满,通过default
分支打印一条消息表示数据被丢弃,避免死锁。
- main函数:
- 创建了
dataCh
和resultCh
两个通道,以及一个WaitGroup
用于等待所有worker完成。
- 启动多个
worker
协程,并为每个协程添加到WaitGroup
中。
- 通过
select
语句向dataCh
通道发送数据。如果dataCh
已满,通过default
分支打印一条消息表示数据被丢弃。
- 发送完所有数据后关闭
dataCh
通道,通知worker
协程没有更多数据。
- 使用
WaitGroup
等待所有worker
协程完成工作,然后关闭resultCh
通道。
- 最后通过
for... range
循环从resultCh
通道接收并打印结果。