2 回答
TA贡献1155条经验 获得超0个赞
一旦通道关闭,您就无法在其上发送更多值,否则它会发生恐慌。这就是你所经历的。
这是因为您启动了多个使用相同通道的 goroutine,并在其上发送值。然后您关闭每个通道中的通道。并且由于它们不同步,一旦第一个 goroutine 到达它关闭它的点,其他人可能(并且他们将)继续在其上发送值:恐慌!
您只能关闭通道一次(尝试关闭已经关闭的通道也会导致恐慌)。当所有发送值的 goroutine 都完成时,你应该这样做。为此,您需要检测所有发送方 goroutine 何时完成。检测这种情况的惯用方法是使用sync.WaitGroup.
对于每个启动的发送者 goroutine,我们将 1 添加到WaitGroupusing WaitGroup.Add()。每个完成发送值的 goroutine 都可以通过调用WaitGroup.Done(). 最好将此作为延迟语句来执行,因此如果您的 goroutine 突然终止(例如恐慌),WaitGroup.Done()仍然会被调用,并且不会让其他 goroutine 挂起(等待赦免 - 一个WaitGroup.Done()永远不会到来的“丢失”调用。 .)
并且WaitGroup.Wait()会等到所有发送方 goroutine 都完成,并且只有在此之后并且只有一次才会关闭通道。我们想要检测这个“全局”完成事件并在处理发送到它的值的过程中关闭通道,所以我们必须在它自己的 goroutine 中执行此操作。
由于我们for ... range在通道上使用了构造,因此接收器 goroutine 将一直运行直到通道关闭。并且由于它在主 goroutine 中运行,因此在从通道正确接收和处理所有值之前,程序不会退出。所述for ... range构建体循环,直到接收到所有的值的信道被关闭之前已被发送。
请注意,下面的解决方案也适用于缓冲和非缓冲通道,无需修改(尝试使用带缓冲的通道ch := make(chan int, 100))。
正确的解决方案(在Go Playground上试试):
func gen(ch chan int, wg *sync.WaitGroup) {
defer wg.Done()
var i int
for {
time.Sleep(time.Millisecond * 10)
ch <- i
i++
// when no more data (e.g. from db, or event stream)
if i > 100 {
break
}
}
}
func receiver(ch chan int) {
for i := range ch {
fmt.Println("received:", i)
}
}
func main() {
ch := make(chan int)
wg := &sync.WaitGroup{}
for i := 0; i < 10; i++ {
wg.Add(1)
go gen(ch, wg)
}
go func() {
wg.Wait()
close(ch)
}()
receiver(ch)
}
笔记:
请注意,重要的是receiver(ch)在主 goroutine中运行,以及WaitGroup在其自己的(非主)goroutine 中等待并关闭通道的代码;而不是相反。如果您要切换这两个,则可能会导致“提前退出”,即并非所有值都可能从通道接收和处理。这是因为 Go 程序在主 goroutine 完成时退出(规范:程序执行)。它不会等待其他(非主)goroutine 完成。因此,如果在主 goroutine 中等待和关闭通道,则在关闭通道后程序可以随时退出,而不是等待在这种情况下将循环接收来自通道的值的另一个 goroutine。
- 2 回答
- 0 关注
- 136 浏览
添加回答
举报