英文:
Sync.WaitGroup, why closer in a goroutine
问题
下面是《Go编程语言》一书中的示例代码。我不明白为什么closer
需要成为它自己的goroutine。我尝试将closer
移到主函数中,但程序会崩溃。有人可以解释一下为什么closer
需要在一个单独的goroutine中吗?
谢谢!
func makeThumbnails(filenames <-chan string, result chan<- int64) int64 {
sizes := make(chan int64)
var wg sync.WaitGroup
for f := range filenames {
wg.Add(1)
go func(f string) {
defer wg.Done()
sizes <- int64(len(f))
}(f)
}
// closer,为什么这个函数需要在一个goroutine中?
go func() {
wg.Wait()
close(sizes)
}()
var total int64
for size := range sizes {
total += size
}
result <- total
return total
}
英文:
Below is the example code in the Go programming book. I do not understand why the closer needs to be its own goroutine. I tried to move the closer into the main but it crashes. Somebody could explain why the closer needs to be in a separate goroutine?
Thanks!
func makeThumbnails(filenames <-chan string, result chan<- int64) int64 {
sizes := make(chan int64)
var wg sync.WaitGroup
for f := range filenames {
wg.Add(1)
go func(f string) {
defer wg.Done()
sizes <- int64(len(f))
}(f)
}
// **closer**, why this guy needs to be in a goroutine???
go func() {
wg.Wait()
close(sizes)
}()
var total int64
for size := range sizes {
total += size
}
result <- total
return total
}
答案1
得分: 4
问题在于sizes
不是一个带缓冲的chan
,所以只有一个匿名goroutine能在需要读取sizes
之前完成。这使得wg.Wait()
永远等待(因为下一个goroutine在sizes <-
上阻塞,无法defer wg.Done()
),从而导致死锁。
通过将关闭操作放在一个单独的goroutine中,它可以在准备好关闭sizes
时关闭该通道,并在此期间从sizes
中处理数据。从根本上讲,这是goroutine的一个很好的用法——启动并忘记关闭!
要使此代码在没有关闭goroutine的情况下工作,只需将sizes
初始化为带有缓冲区的chan,缓冲区大小大于等于filenames
的长度。
func makeThumbnails(filenames <-chan string, result chan<- int64) int64 {
sizes := make(chan int64, 10) // 带缓冲的通道,现在!
// 如果filenames发送的字符串超过10个,我们就有麻烦了!!
var wg sync.WaitGroup
for f := range filenames {
wg.Add(1)
go func(f string) {
defer wg.Done()
sizes <- int64(len(f))
}(f)
}
// **关闭者**,这个人不需要成为一个goroutine!!
wg.Wait()
close(sizes)
var total int64
for size := range sizes {
total += size
}
result <- total
return total
}
然而,由于filenames
的长度在运行时是不可知的,所以这样做并不容易。你需要遍历filenames
,将其存储到一个切片中,然后初始化sizes
并在range filenamesSlice
上进行for
循环... 是的,在这一点上,你基本上已经重写了整个函数。
英文:
The problem is that sizes
is not a buffered chan
, so only one of the anonymous goroutines can actually complete before sizes
needs to be read from. That makes wg.Wait()
wait forever (since the next goroutine is blocking on sizes <-
and can't defer wg.Done()
) and deadlocks.
By throwing the closer in a separate goroutine, it can close the sizes
chan whenever it's ready to do so, and process from sizes
in between. Ultimately this is a great use of a goroutine -- fire and forget closing!
To make this code work without the closer goroutine, you can simply initialize sizes
as a buffered chan with a buffer >= the length of filenames
.
func makeThumbnails(filenames <-chan string, result chan<- int64) int64 {
sizes := make(chan int64, 10) // buffered channel, now!
// if filenames sends more than 10 strings, though, we're in trouble!!
var wg sync.WaitGroup
for f := range filenames {
wg.Add(1)
go func(f string) {
defer wg.Done()
sizes <- int64(len(f))
}(f)
}
// **closer**, this guy doesn't need to be a goroutine!!
wg.Wait()
close(sizes)
var total int64
for size := range sizes {
total += size
}
result <- total
return total
}
However since filenames
's length is unknowable at runtime, it's not possible to do this easily. You'd have to read through filenames
, store it into a slice, then initialize sizes and for
over range filenamesSlice
and.... yeah basically you've just re-written the whole function at that point.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论