英文:
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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。


评论