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

如何解决Go通道死锁?

如何解决Go通道死锁?

Go
繁星点点滴滴 2023-07-26 15:45:22
我正在学习Go编程语言,最近我遇到一个问题,我尝试了很多方法来运行我的代码,但我无法正确运行。我怎样才能改变我的程序来做到这一点?package mainimport (    "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()}
查看完整描述

4 回答

?
慕桂英3389331

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

正在进行一场数据竞赛subTask。subTask任务初始化 goroutine在不同步的情况下读取和写入变量。


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


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


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。无法保证任务初始化 Goroutines 在主 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()


竞争检测器报告上述两种数据竞争。

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


查看完整回答
反对 回复 2023-07-26
?
肥皂起泡泡

TA贡献1829条经验 获得超6个赞

如果通道为零,则<-c来自 c 的接收将永远阻塞。因此出现死锁。通道可能为 nil 的原因是,在执行 goroutine 接收时,第一个 for 循环中的其中一个 goroutine 可能尚未执行。

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

添加睡眠可以让您看到差异,但您实际上应该解决这个问题。

可能出现问题的另一件事是, subTask := make([]Task, 100)此语句在切片中创建 100 个空任务 obj,并追加添加更多内容,因此长度最终会增长到 200。

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


查看完整回答
反对 回复 2023-07-26
?
慕侠2389804

TA贡献1719条经验 获得超6个赞

您可以考虑一串任务,而不是一部分任务。


我认为这保留了您创建 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)

    }

}

在操场上奔跑


查看完整回答
反对 回复 2023-07-26
?
HUH函数

TA贡献1836条经验 获得超4个赞

一个问题是您要追加到切片而不是更新现有的切片项。而且你不需要缓冲的chan。


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

}


查看完整回答
反对 回复 2023-07-26
  • 4 回答
  • 0 关注
  • 106 浏览
慕课专栏
更多

添加回答

举报

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