场景一分析
- 问题原因:在多协程并发读取Channel数据时,某个子协程出现异常,未能正常处理完Channel中的数据,导致其他协程可能一直阻塞等待该Channel,从而造成资源泄露。
- 预防和处理方法:
- 使用
defer
和recover
:在子协程中使用defer
和recover
机制捕获异常,确保即使发生异常,也能正常关闭Channel或进行必要的清理操作。
- 监控子协程状态:可以使用一个额外的Channel来传递子协程的完成状态,主协程通过监听该状态Channel来判断所有子协程是否正常完成。
场景一代码实现
package main
import (
"fmt"
)
func worker(id int, ch <-chan int, done chan<- struct{}) {
defer func() {
if r := recover(); r != nil {
fmt.Printf("Worker %d recovered from panic: %v\n", id, r)
}
done <- struct{}{}
}()
for data := range ch {
// 模拟可能出现异常的操作
if data == 5 {
panic("Simulated panic")
}
fmt.Printf("Worker %d received: %d\n", id, data)
}
}
func main() {
ch := make(chan int)
numWorkers := 3
done := make(chan struct{}, numWorkers)
for i := 0; i < numWorkers; i++ {
go worker(i, ch, done)
}
// 发送数据
for i := 0; i < 10; i++ {
ch <- i
}
close(ch)
// 等待所有子协程完成
for i := 0; i < numWorkers; i++ {
<-done
}
close(done)
fmt.Println("All workers have finished")
}
场景二分析
- 问题原因:在生产者 - 消费者模型中,生产者结束后,消费者可能还在等待接收数据,因为没有明确的信号告知消费者生产者已结束,从而导致Channel一直处于未关闭状态,造成泄露。
- 改进方法:
- 明确关闭Channel:生产者完成数据发送后,关闭Channel,这样消费者在读取完所有数据后,会收到通道关闭的信号,从而正常退出。
- 使用
context
:可以结合context
来控制生产者和消费者的生命周期,当生产者结束时,通过context
通知消费者退出。
场景二代码实现
方法一:明确关闭Channel
package main
import (
"fmt"
)
func producer(ch chan<- int) {
for i := 0; i < 10; i++ {
ch <- i
}
close(ch)
}
func consumer(ch <-chan int) {
for data := range ch {
fmt.Printf("Consumer received: %d\n", data)
}
fmt.Println("Consumer finished")
}
func main() {
ch := make(chan int)
go producer(ch)
consumer(ch)
}
方法二:使用context
package main
import (
"context"
"fmt"
"time"
)
func producer(ctx context.Context, ch chan<- int) {
for i := 0; i < 10; i++ {
select {
case ch <- i:
case <-ctx.Done():
return
}
}
close(ch)
}
func consumer(ctx context.Context, ch <-chan int) {
for {
select {
case data, ok := <-ch:
if!ok {
fmt.Println("Consumer finished")
return
}
fmt.Printf("Consumer received: %d\n", data)
case <-ctx.Done():
return
}
}
}
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
ch := make(chan int)
go producer(ctx, ch)
consumer(ctx, ch)
}