为什么会出现致命错误:所有的goroutine都睡着了-死锁!在这个代码?

hmtdttj4  于 2023-05-20  发布在  Go
关注(0)|答案(4)|浏览(183)

这是参考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通道

bq3bfh9z

bq3bfh9z1#

你的通道是无缓冲的(你在make()操作通道时没有指定任何缓冲区大小)。这意味着,在读取写入的值之前,对通道的写入将被阻止。在调用wg.Wait()之后,你从通道中读取,所以没有任何东西被读取,所有的goroutine都卡在阻塞写上。
也就是说,你不需要等待组在这里。当你不知道你的goroutine何时完成时,等待组是很好的,但是你会把结果发送回来,所以你知道。下面是一个示例代码,它做的事情与您尝试做的事情类似(使用假的worker有效负载)。

package main

import (
    "fmt"
    "time"
)

func main() {
    var procs int = 0
    filenames := []string{"file1", "file2", "file3", "file4"}
    mychan := make(chan string)
    for _, f := range filenames {
        procs += 1
        // worker
        go func(f string) {
            fmt.Printf("Worker processing %v\n", f)
            time.Sleep(time.Second)
            mychan <- f
        }(f)
    }

    for i := 0; i < procs; i++ {
        select {
        case msg := <-mychan:
            fmt.Printf("got %v from worker channel\n", msg)
        }
    }
}

在这里的操场上测试一下https://play.golang.org/p/RtMkYbAqtGO

vc9ivgsu

vc9ivgsu2#

虽然已经有一段时间了,因为这个问题被提出来,我遇到了同样的问题。最初,我的main看起来像下面这样:

func main() {
   filenames := make(chan string, len(os.Args))
   for _, f := range os.Args[1:] {
       filenames <- f
   }
   sizes := makeThumbnails6(filenames)
   close(filenames)
   log.Println("Total size: ", sizes)}

此版本死锁,因为makeThumbnails6中的调用range filenames是同步的,因此main中的close(filenames)从未被调用。makeThumbnails6中的通道是无缓冲的,所以goroutines在试图发送回大小时会阻塞。
解决方案是在main中进行函数调用之前移动close(filenames)

uz75evzq

uz75evzq3#

有点晚了,但我想分享我对这个好问题的理解。
在goroutine中使用closer函数的原因是,否则它会导致依赖性缺陷。
怎么做?
wg.Wait()将阻塞,直到相关的goroutine返回(在调用wg.Done()之后)。
这些例程只能在大小通道被接收器“排空”时返回。
“排出”sizes的代码--从而释放了goroutine--是“for”循环,它接收来自sizes的信息。它是在更接近功能之后出现的。
这意味着它将阻止它所依赖的代码实际执行并永远等待。

jvidinwx

jvidinwx4#

密码错了。简而言之,通道sizes是无缓冲的。为了解决这个问题,我们需要在创建sizes时使用具有足够容量的缓冲通道。如图所示,一个内衬修复就足够了。这里我只是做了一个简单的假设,1024是足够大的。

func makeThumbnails6(filenames chan string) int64 {
    sizes := make(chan int64, 1024)    // CHANGE 
    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
}

相关问题