这是参考Go编程语言-第8章第238页中的以下代码,复制自this链接
// makeThumbnails6 makes thumbnails for each file received from the channel.
// It returns the number of bytes occupied by the files it creates.
func makeThumbnails6(filenames <-chan string) int64 {
sizes := make(chan int64)
var wg sync.WaitGroup // number of working goroutines
for f := range filenames {
wg.Add(1)
// worker
go func(f string) {
defer wg.Done()
thumb, err := thumbnail.ImageFile(f)
if err != nil {
log.Println(err)
return
}
info, _ := os.Stat(thumb) // OK to ignore error
fmt.Println(info.Size())
sizes <- info.Size()
}(f)
}
// closer
go func() {
wg.Wait()
close(sizes)
}()
var total int64
for size := range sizes {
total += size
}
return total
}
为什么我们需要在一个goroutine中放置closer?为什么下面不能工作?
// closer
// go func() {
fmt.Println("waiting for reset")
wg.Wait()
fmt.Println("closing sizes")
close(sizes)
// }()
如果我尝试运行上面的代码,它会给出:
等待复位
3547
2793
致命错误:所有的goroutine都睡着了-死锁!
为什么上面会出现死锁?fyi,在调用makeThumbnail6
的方法中,我确实关闭了filenames
通道
4条答案
按热度按时间bq3bfh9z1#
你的通道是无缓冲的(你在make()操作通道时没有指定任何缓冲区大小)。这意味着,在读取写入的值之前,对通道的写入将被阻止。在调用wg.Wait()之后,你从通道中读取,所以没有任何东西被读取,所有的goroutine都卡在阻塞写上。
也就是说,你不需要等待组在这里。当你不知道你的goroutine何时完成时,等待组是很好的,但是你会把结果发送回来,所以你知道。下面是一个示例代码,它做的事情与您尝试做的事情类似(使用假的worker有效负载)。
在这里的操场上测试一下https://play.golang.org/p/RtMkYbAqtGO
vc9ivgsu2#
虽然已经有一段时间了,因为这个问题被提出来,我遇到了同样的问题。最初,我的main看起来像下面这样:
此版本死锁,因为
makeThumbnails6
中的调用range filenames
是同步的,因此main中的close(filenames)
从未被调用。makeThumbnails6
中的通道是无缓冲的,所以goroutines在试图发送回大小时会阻塞。解决方案是在main中进行函数调用之前移动
close(filenames)
。uz75evzq3#
有点晚了,但我想分享我对这个好问题的理解。
在goroutine中使用closer函数的原因是,否则它会导致依赖性缺陷。
怎么做?
wg.Wait()将阻塞,直到相关的goroutine返回(在调用wg.Done()之后)。
这些例程只能在大小通道被接收器“排空”时返回。
“排出”sizes的代码--从而释放了goroutine--是“for”循环,它接收来自sizes的信息。它是在更接近功能之后出现的。
这意味着它将阻止它所依赖的代码实际执行并永远等待。
jvidinwx4#
密码错了。简而言之,通道
sizes
是无缓冲的。为了解决这个问题,我们需要在创建sizes
时使用具有足够容量的缓冲通道。如图所示,一个内衬修复就足够了。这里我只是做了一个简单的假设,1024是足够大的。