第二题:
创建一个生产器和接收器,再建立一个无缓冲的channel。生产器负责把数据放进管道里,接收器负责把管道里面的数据打印出来。这里我们开5个协程把数据打印出来。
直接上代码!
package main
import (
"fmt"
)
func receive(c <-chan int) {
/*for v := range c {
fmt.Println("received:", v)
}*/
for i := 0; i <= 1; i++ {
go func() {
for v := range c {
fmt.Println(v)
}
}()
}
}
func generator() <-chan int {
c := make(chan int)
for i := 0; i <= 9; i++ {
go func(i int) {
for j := 0; j <= 9; j++ {
temp := i*100 + 20 + j
c <- temp
}
close(c)
}(i)
}
return c
}
func main() {
c := generator()
receive(c)
}
埋了个小坑,跑上面的代码,在这里是不会有任何输出的。
原因是main函数结束时程序就退出了,没有给goroutine足够运行的时间来打印输出。
整个流程是并发执行的,main函数、generator的goroutine、receive的goroutine都是并发运行。
但是问题是main函数和generator很快就结束了,程序退出,receive的goroutine来不及打印数据。
解决方法就是让main函数等一等receive的goroutine。我们在main函数中加上一句:
time.Sleep(time.Second * 5)
这时看到可以顺利输出了。
但是...
但是却panic了。为什么呢?
因为generator()把消息发送到了关闭的管道。是因为生成器goroutine和接收goroutine的生命周期没有控制好导致的。
主要原因在于,接收的goroutine一旦从通道接收完所有的数据并退出,通道就会被关闭。
而此时,生成器goroutine可能还在向这个通道发送数据,于是产生了panic。
要避免这种情况,需要确保:
1、接收goroutine在最后一个生成器goroutine退出之前不能退出。
2、生成器goroutine在关闭通道之前,必须保证接收goroutine仍在运行。
问题出在生成器中close(c)这一行。这里每个goroutine都在自己完成后关闭了通道c。
按照程序逻辑,通道c应该在最后一个goroutine完成时关闭一次,而不是每个goroutine都关闭。所以应该只在主goroutine中关闭c。这里我们用WaitGroup来同步。
func generator() <-chan int {
c := make(chan int)
var wg sync.WaitGroup
wg.Add(10) // 添加10个goroutine
for i := 0; i < 10; i++ {
go func() {
// 生成数据
wg.Done() // goroutine结束
}()
}
go func() {
wg.Wait() // 等待所有goroutine完成
close(c) // 关闭通道,仅关闭一次
}()
return c
}
顺利输出!!