我有一个示例代码(您可以在Go Playground上找到它):
package main
import (
"fmt"
"sync"
"time"
)
func main() {
messages := make(chan int)
var wg sync.WaitGroup
var result []int
// you can also add these one at
// a time if you need to
wg.Add(1)
go func() {
defer wg.Done()
time.Sleep(time.Second * 1)
messages <- 1
}()
wg.Add(1)
go func() {
defer wg.Done()
time.Sleep(time.Second * 1)
messages <- 2
}()
wg.Add(1)
go func() {
defer wg.Done()
time.Sleep(time.Second * 1)
messages <- 3
}()
go func() {
for i := range messages {
fmt.Println(i)
result = append(result, i)
}
}()
wg.Wait()
fmt.Println(result)
}
我得到了这个输出:
2
1
[2 1]
我想我知道为什么会这样,但我无法解决。 WaitGroup中有3个项目我指的是三个goroutine,第四个groutine消耗来自通道的数据。当最后一个灌浆说wg.Done()
时程序结束了,因为wg.Wait()表示每个goroutine都完成了,最后的goroutine结果是第4个goroutine无法消耗,因为程序结束了。我尝试在第4个函数中添加带有wg.Add(1)和wg.Done()的加号,但在这种情况下我遇到了死锁。
答案 0 :(得分:4)
你产生的最后一个goroutine - 那个打算收集结果的goroutine - 不会被main()
等待,因此返回wg.Wait()
,main()
退出并获得剩余的goroutines。
据说当时只剩下一个收集goroutine但它无法更新切片。
另请注意,由于您在程序中进行数据竞争的原因相同:在main()
读取结果片段时,它 可以安全地阅读它 - 也就是说,作者是否完成了写作。
一个简单的解决方法是为该goroutine添加wg.Add(1)
,并在其中添加defer wg.Done()
。
更好的解决方案是在close()
之后messages
wg.Wait()
频道
并且在从切片读取之前。这将使收集goroutine的range
循环终止,这也将在goroutine和main()
之间创建一个正确的同步点。
答案 1 :(得分:2)
关闭频道是用于信令的惯用Go模式,如果您关闭缓冲频道,则消费者可以读取所有排队的数据然后停止。
此代码可正常运行:
func main() {
messages := make(chan int)
var wg sync.WaitGroup
var result []int
// you can also add these one at
// a time if you need to
wg.Add(1)
go func() {
defer wg.Done()
time.Sleep(time.Second * 1)
messages <- 1
}()
wg.Add(1)
go func() {
defer wg.Done()
time.Sleep(time.Second * 1)
messages <- 2
}()
wg.Add(1)
go func() {
defer wg.Done()
time.Sleep(time.Second * 1)
messages <- 3
}()
// this goroutine added to signal end of data stream
// by closing messages channel
go func() {
wg.Wait()
close(messages)
}()
// if you need this to happen inside a go routine,
// this channel is used for signaling end of the work,
// also another sync.WaitGroup could be used, but for just one
// goroutine, a single channle as a signal makes sense (there is no
// groups)
done := make(chan struct{})
go func() {
defer close(done)
for i := range messages {
fmt.Println(i)
result = append(result, i)
}
}()
<-done
fmt.Println(result)
}
如您所见,我们刚刚添加了另一个关闭messages
频道的goroutine,当所有制作人都完成时。
答案 2 :(得分:0)
kostix 的答案是正确的,直到他们提到
一个简单的解决方法是为该goroutine添加
wg.Add(1)
并在其中延迟wg.Done()
。
这将导致您的循环在不关闭消息通道的情况下永远无法完成!因此,主goroutine将在您的最后一个“收集” goroutine完成之前再次完成。如果将goroutine绑定到wg
WaitGroup,它将永远不会发送Done()
信号,也会导致错误。
然后当他们提到
更好的解决方案是在
close()
之后且从切片读取之前,wg.Wait()
个消息通道
他们建议的放置位置将再次给您同样的错误,因为您将在同一WaitGroup wg
上等待。在您上一个“正在收集”的goroutine会继续在您的messages
频道中寻找更多消息,并且永远不会到达延迟的wg.Done()
然后 Alex Yu 的注释通过在完全读取结果之前等待来解决它,这是一个很好的解决方法。但是,如果您希望收集的goroutine立即开始,而不是等待所有以前的goroutine(写入messages
通道)在它开始从所述通道读取之前完成,那么我建议您...
创建一个结果WaitGroup Add(1)
,然后再开始最后一个“收集”程序中的defer wgResult.Done()
,然后最后在wg.Wait()
和{ {1}},您应该fmt.Println(result)
和close(messages)
。
这允许您所有的go例程尽快启动,并且仅在编写goroutine和阅读goroutine时需要等待。
下面是带有建议解决方案的GoPlayground链接