【问题标题】:How to resolve Go channel deadlock?如何解决 Go 通道死锁?
【发布时间】:2020-01-24 01:46:34
【问题描述】:

我正在学习 Go 编程语言,最近我遇到一个问题,我尝试了很多方法来运行我的代码,但我无法正确运行。我怎样才能改变我的程序来做到这一点?

package main

import (
    "fmt"
    "sync"
)

type Task struct {
    Id       int
    Callback chan int
}

func main() {
    var wg sync.WaitGroup
    subTask := make([]Task, 100)
    for i := 0; i < 100; i++ {
        go func(i int) {
            task := Task{
                Id:       i,
                Callback: make(chan int, 1),
            }
            task.Callback <- i
            subTask = append(subTask, task)
        }(i)
    }

    for _, v := range subTask {
        wg.Add(1)
        go func(v Task) {
            defer wg.Done()
            x := <-v.Callback
            fmt.Printf("%d ", x)
        }(v)
    }
    wg.Wait()
}

【问题讨论】:

    标签: go deadlock channel


    【解决方案1】:

    如果一个频道是 nil,&lt;-c 从 c 接收将永远阻塞。因此陷入僵局 通道可能为 nil 的原因是第一个 for 循环中的一个 goroutine 在执行 goroutine 接收时可能尚未执行。

    因此,如果您假设第一个 for 循环中的所有 goroutine 在第二个 for 循环开始之前执行,您的代码就可以正常工作。

    在睡眠中添加可以向您展示差异,但您实际上应该解决的是问题。

    还有一个问题可能是 subTask := make([]Task, 100) 这个语句在 slice 中创建了 100 个空任务 obj,然后 append 增加了更多,所以长度最终增长到 200。

    https://play.golang.org/p/4bZDJ2zvKdF

    【讨论】:

      【解决方案2】:

      一个问题是您要附加到切片而不是更新现有的切片项。此外,您不需要缓冲通道。

      func main() {
          subTask := make([]Task, 100)
          for i := range subTask {
              go func(i int) {
                  subTask[i] = Task{i, make(chan int)}
                  subTask[i].Callback <- i
              }(i)
          }
      
          var wg sync.WaitGroup
          wg.Add(len(subTask))
          for _, v := range subTask {
              go func(v Task) {
                  defer wg.Done()
                  fmt.Println(<-v.Callback)
              }(v)
          }
          wg.Wait()
      }
      

      【讨论】:

      • 警告:正如 Cerise 所指出的,在我上面的代码中仍然存在使用切片的竞争条件。最好避免使用另一个 chan 在 go-routines 之间共享一个切片 - 请参阅我稍后的答案。
      【解决方案3】:

      subTask 上存在数据竞争。任务初始化 goroutine 读取和写入变量 subTask 没有同步。

      程序的目的是创建和初始化一个包含 100 个 Task 值的切片,但它创建了一个具有 100 个零值 Tasks 的切片,并附加了 100 个已初始化的 Tasks(忽略数据竞争问题刚刚提到)。

      通过将任务分配给切片元素来解决这两个问题:

      for i := 0; i < 100; i++ {
          go func(i int) {
              task := Task{
                  Id:       i,
                  Callback: make(chan int, 1),
              }
              task.Callback <- i
              subTask[i] = task
          }(i)
      }
      

      subTask 元素存在数据竞争。不能保证任务初始化 goroutine 在主 goroutine 覆盖这些元素之前完成对元素的写入。通过使用等待组来协调初始化 goroutine 和主 goroutine 的完成来修复:

      subTask := make([]Task, 100)
      for i := 0; i < 100; i++ {
          wg.Add(1)
          go func(i int) {
              task := Task{
                  Id:       i,
                  Callback: make(chan int, 1),
              }
              task.Callback <- i
              subTask[i] = task
              wg.Done()
          }(i)
      }
      wg.Wait()
      

      Run the code on the playground.

      race detector 报告上述两种数据竞争。

      如果问题中的代码是实际代码,而不是用于提问的最小示例,则根本不需要 goroutine。

      【讨论】:

        【解决方案4】:

        你可以考虑一个任务而不是一个任务片。

        我认为这保留了您创建 100 个独立读写通道的最初想法。它还避免了数据竞争。

        func main() {
            subTasks := make(chan Task)
            var wg sync.WaitGroup
            for i := 0; i < 100; i++ {
                wg.Add(1)
                go func(i int) {
                    defer wg.Done()
                    task := Task{i, make(chan int)}
                    subTasks <- task
                    task.Callback <- i
                }(i)
            }
        
            go func() {
                wg.Wait()
                close(subTasks)
            }()
        
            for v := range subTasks {
                go func(v Task) {
                    fmt.Println(<-v.Callback)
                }(v)
            }
        }
        

        Run in the playground

        【讨论】:

          猜你喜欢
          • 2018-02-02
          • 2017-06-24
          • 1970-01-01
          • 2021-12-25
          • 1970-01-01
          • 2019-05-10
          • 1970-01-01
          • 2017-09-06
          • 1970-01-01
          相关资源
          最近更新 更多