如何解决 Go 通道死锁?
Posted
技术标签:
【中文标题】如何解决 Go 通道死锁?【英文标题】:How to resolve Go channel deadlock? 【发布时间】: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()
【问题讨论】:
【参考方案1】:如果一个频道是 nil,<-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] = Taski, 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 个零值 Task
s 的切片,并附加了 100 个已初始化的 Task
s(忽略数据竞争问题刚刚提到)。
通过将任务分配给切片元素来解决这两个问题:
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 := Taski, 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
【讨论】:
以上是关于如何解决 Go 通道死锁?的主要内容,如果未能解决你的问题,请参考以下文章