为了账号安全,请及时绑定邮箱和手机立即绑定

为什么会出现致命错误:所有的 goroutines 都睡着了——死锁!在这段代码中?

为什么会出现致命错误:所有的 goroutines 都睡着了——死锁!在这段代码中?

Go
红糖糍粑 2023-04-17 16:11:40
这是参考 The Go Programming Language - Chapter 8 p.238 中的以下代码,从该链接下面复制// 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}为什么我们需要将 closer 放在 goroutine 中?为什么下面不能工作?// closer        // go func() {        fmt.Println("waiting for reset")                wg.Wait()        fmt.Println("closing sizes")                close(sizes)        // }()如果我尝试运行上面的代码,它会给出:等待重置35472793致命错误:所有 goroutines 都睡着了 - 死锁!为什么上面会出现死锁?fyi,在调用的方法中makeThumbnail6我确实关闭了filenames频道
查看完整描述

3 回答

?
手掌心

TA贡献1942条经验 获得超3个赞

您的频道是无缓冲的(您在 make()ing 频道时没有指定任何缓冲区大小)。这意味着写入通道会阻塞,直到写入的值被读取。在调用 wg.Wait() 之后,您从通道读取数据,因此不会读取任何内容,并且您的所有 goroutine 都会卡在阻塞写入中。


也就是说,您在这里不需要 WaitGroup。当您不知道 goroutine 何时完成时,WaitGroups 很有用,但您正在发回结果,所以您知道。这是一个示例代码,它执行与您尝试执行的操作类似的操作(使用伪造的工作负载)。


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)

        }

    }

}


查看完整回答
反对 回复 2023-04-17
?
aluckdog

TA贡献1847条经验 获得超7个赞

虽然提出问题已经有一段时间了,但我遇到了同样的问题。最初我的主要内容如下所示:


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)}

此版本死锁,因为调用range filenames是makeThumbnails6同步的,因此close(filenames)in main 从未被调用。输入的通道makeThumbnails6是无缓冲的,因此 goroutines 在尝试发回大小时会阻塞。


close(filenames)解决方案是在 main 中进行函数调用之前移动。


查看完整回答
反对 回复 2023-04-17
?
哔哔one

TA贡献1854条经验 获得超8个赞

代码错误。简而言之,通道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

}


查看完整回答
反对 回复 2023-04-17
  • 3 回答
  • 0 关注
  • 130 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信