【问题标题】:Why am I getting all goroutines are asleep when I close the channel after waiting?为什么等待后关闭通道时所有 goroutine 都处于睡眠状态?
【发布时间】:2021-08-26 08:30:17
【问题描述】:

以下是代码:

func makeData() map[string][]Data {
    m := make(map[string][]Data)
    s := "abcdefghijklmno"

    for i, c := range s {
        data := []Data{
            {value: "hey_" + string(c), id: i * i},
            {value: "hello_" + string(c) + string(c), id: i + i},
            {value: "bye_" + string(c), id: i + 1},
        }
        m[strconv.Itoa(i)] = data
    }

    return m
}

func process(key string, value []Data) (*Result, error) {
    if key == "hey_a" {
        return nil, errors.New("error")
    }

    res := Result{data: Data{value: "hi", id: 0}, id: 1}
    return &res, nil
}

func main() {
    runtime.GOMAXPROCS(runtime.NumCPU())
    m := makeData()
    errg := new(errgroup.Group)

    mapChan := make(chan StringAndData)
    sliceChan := make(chan *Result)

    for key, value := range m {
        key := key
        value := value

        errg.Go(func() error {
            return func(key string, value []Data) error {
                res, err := process(key, value)
                if err != nil {
                    return err
                }
                if res == nil {
                    return nil
                }

                if res.data.id == 1 {
                    mapChan <- StringAndData{
                        str:  key,
                        data: res.data,
                    }
                    return nil
                }

                sliceChan <- res
                return nil

            }(key, value)
        })
    }

    if err := errg.Wait(); err != nil {
        fmt.Println("error")
    } else {
        fmt.Println("success")
    }

    close(mapChan)
    close(sliceChan)

    for ac := range mapChan {
        fmt.Println(ac.str)
    }
}

type Data struct {
    value string
    id    int
}

type Result struct {
    data Data
    id   int
}

type StringAndData struct {
    str  string
    data Data
}

Playground

我收到了fatal error: all goroutines are asleep - deadlock!,但我在errg.Wait() 之后关闭了频道,我无法理解原因。

我正在尝试打印使用range 关闭通道后从通道获得的值。

我是 go 的通道和并发新手,希望能提供任何帮助!

编辑 添加了游乐场链接中的所有代码

【问题讨论】:

  • merrg 来自哪里?请发布最小的、可重现的示例。
  • @jub0bs 请查看操场链接。是errorGroup
  • 那是最小的吗?此外,SO 问题应该是独立的。
  • @jub0bs 更新了更多代码
  • 基本上这意味着当errg.Wait() 完成时,仍有未完成的goroutines(等待一些资源)。 Go 检测到所有这些 goroutine(包括等待 mapChan 的 main)在某些资源上被阻塞。首先要做的是放弃使用errgroup:在您对基本知识感到满意之前,请不要陷入使用花哨东西的陷阱。使用普通通道使您的代码工作(可能是sync.WaitGroup)。

标签: go concurrency deadlock channel


【解决方案1】:

查看您的代码,有两件事会导致死锁:

  • errg.Wait() 阻止主 goroutine 的执行,直到所有初始化的 goroutine 完成。但是,每个 goroutine 在尝试写入 mapChan 时都会被阻止,因为您永远无法读取它(因为它位于 errg.Wait() 下方)。
  • 您从未阅读过 sliceChan 的内容,因此那里可能存在死锁。

Here 是修改后的 Playground 代码的链接,但大部分更改都在 main 函数中。

func main() {
    runtime.GOMAXPROCS(runtime.NumCPU())
    m := makeData()
    errg := new(errgroup.Group)

    mapChan := make(chan StringAndData)
    sliceChan := make(chan *Result)
    mapDone := make(chan bool)
    sliceDone := make(chan bool)

    go func(){
        for ac := range mapChan {
            fmt.Println(ac.str)
        }
        mapDone <- true
    }()
    go func(){
        for ac := range sliceChan {
            fmt.Println(ac)
        }
        sliceDone <- true
    }()

    for key, value := range m {
        key := key
        value := value

        errg.Go(func() error {
            return func(key string, value []Data) error {
                res, err := process(key, value)
                if err != nil {
                    return err
                }
                if res == nil {
                    return nil
                }

                if res.data.id == 1 {
                    mapChan <- StringAndData{
                        str:  key,
                        data: res.data,
                    }
                    return nil
                }

                sliceChan <- res
                return nil

            }(key, value)
        })
    }

    if err := errg.Wait(); err != nil {
        fmt.Println("error")
    } else {
        fmt.Println("success")
    }

    close(mapChan)
    close(sliceChan)
    <-mapDone
    <-sliceDone
    fmt.Println("finished")

}

基本上,我更改了从mapChansliceChan 通道读取值的方式。这是在单独的 goroutine 中完成的,因此不会阻止从这些通道中读取。

添加mapDonesliceDone 通道只是为了确保在main goroutine 完成之前读取所有数据。

【讨论】:

    猜你喜欢
    • 2017-02-23
    • 2020-12-24
    • 2019-07-26
    • 2022-08-16
    • 2014-04-22
    • 1970-01-01
    • 1970-01-01
    • 2013-11-22
    • 1970-01-01
    相关资源
    最近更新 更多