为什么会出现致命错误:所有goroutine都在睡眠中-死锁!在这段代码中?

时间:2018-10-03 16:58:47

标签: go

这是参考《 Go编程语言-第8章,第238页,从下面的this链接复制过来的以下代码》

key

为什么我们需要将闭门器放在goroutine中?为什么下面不能工作?

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

如果我尝试运行上面的代码,它将给出:

  

等待重置
  3547
  2793
  致命错误:所有goroutine都在睡眠中-死锁!

为什么上面有一个死锁? fyi,在调用// closer // go func() { fmt.Println("waiting for reset") wg.Wait() fmt.Println("closing sizes") close(sizes) // }() 的方法中,我确实关闭了makeThumbnail6频道

3 个答案:

答案 0 :(得分:1)

您的通道未缓冲(在对通道进行make()处理时未指定任何缓冲区大小)。这意味着对通道的写入将一直阻塞,直到读取了写入的值。而且,在调用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)
        }
    }
}

https://play.golang.org/p/RtMkYbAqtGO这里的操场上进行测试

答案 1 :(得分:0)

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

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是同步的,因此该版本陷入僵局,因此从不调用main中的close(filenames)makeThumbnails6中的频道未缓冲,因此goroutines在尝试发送回大小时会阻塞。

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

答案 2 :(得分:-1)

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