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

如何修复2个通道互相死锁

如何修复2个通道互相死锁

Go
慕妹3146593 2023-08-14 15:14:40
我有这段Go代码。我需要具备这种能力:在一个地方写入通道,然后在另一个地方读出它们(反之亦然):package mainimport "fmt"var ch1=make(chan int)var ch2=make(chan int)func f1() {    select {    case <- ch1:fmt.Println("ch1")    default: fmt.Println("default")    }}func f2() {    select {    case <- ch2:fmt.Println("ch2")    default: fmt.Println("default")    }}func main() {    go f1()    go f2()    ch1<-1    ch2<-2}它总是像这样打印:defaultch1fatal error: all goroutines are asleep - deadlock!goroutine 1 [chan send]:main.main()    /tmp/sandbox970110849/prog.go:22 +0xa0此外我尝试了这个:package mainimport (    "fmt"    "sync")var ch1=make(chan int)var ch2=make(chan int)func f1() {    select {    case <- ch1:fmt.Println("ch1")    default: fmt.Println("default")    }}func f2() {    select {    case <- ch2:fmt.Println("ch2")    default: fmt.Println("default")    }}func w1() {    ch1 <-1}func w2() {    ch2 <-1}func main() {    var wg sync.WaitGroup    wg.Add(4)    go f1()    go f2()    go w1()    go w2()    wg.Wait()}这次错误更多:defaultch2fatal error: all goroutines are asleep - deadlock!goroutine 1 [semacquire]:sync.runtime_Semacquire(0x40e028, 0x0)    /usr/local/go/src/runtime/sema.go:56 +0x40sync.(*WaitGroup).Wait(0x40e020, 0x14b720)    /usr/local/go/src/sync/waitgroup.go:130 +0x60main.main()    /tmp/sandbox916639182/prog.go:36 +0x100goroutine 8 [chan send]:main.w1()    /tmp/sandbox916639182/prog.go:23 +0x40created by main.main    /tmp/sandbox916639182/prog.go:34 +0xc0我哪里出了问题以及如何解决?
查看完整描述

2 回答

?
幕布斯7119047

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

您的main()函数尝试在所有通道上发送,并且仅一次性尝试在单独的并发 goroutine 中从这些通道中读取数据。这是否成功取决于 goroutine 调度程序。如果非阻塞接收的安排f2()早于发送main(),那么后面的发送main()将永远阻塞(没有人会再次尝试接收ch2)。

摆脱死锁的一种方法是使用接收操作而不是非阻塞接收(在Go Playground上尝试一下):

func f1() {

    <-ch1

    fmt.Println("ch1")

}

func f2() {

    <-ch2

    fmt.Println("ch2")

}

因此,无论何时main()到达在这些通道上发送值的点,总会有一个接收器准备好继续,因此不会main()被卡住。

请注意,当main()返回时,应用程序结束,它不会等待非主 goroutine 完成。因此您可能看不到ch1ch2打印在控制台上。

如果您的目的是在您的应用程序存在之前等待所有 goroutine 完成其工作,请使用sync.WaitGroup它(在Go Playground上尝试):

var ch1 = make(chan int)

var ch2 = make(chan int)


var wg sync.WaitGroup


func f1() {

    defer wg.Done()

    <-ch1

    fmt.Println("ch1")

}


func f2() {

    defer wg.Done()

    <-ch2

    fmt.Println("ch2")

}


func main() {

    wg.Add(1)

    go f1()

    wg.Add(1)

    go f2()


    ch1 <- 1

    ch2 <- 2

    wg.Wait()

}


另一种选择是为通道提供 1 的缓冲区,这样就可以在通道上发送 1 个值,而无需准备好从通道接收的接收器(在Go Playgroundmain()上尝试这个):

var ch1 = make(chan int, 1)
var ch2 = make(chan int, 1)

有了这个,就可以在没有和 的情况main()下继续,所以同样,不能保证您会看到任何打印内容。f1()f2()


查看完整回答
反对 回复 2023-08-14
?
慕少森

TA贡献2019条经验 获得超9个赞

这里使用的通道是无缓冲通道。

  • 当主协程启动时,它会创建两个新的协程 f1 和 f2。

  • 当执行 f1 或 f2 时,它将检查通道中是否有值,否则它将打印默认消息并退出。

  • 在理想情况下,通道将首先发布值,然后通过 goroutine 接收它

  • 实际情况是 Goroutine 通过打印默认情况退出,但主 Goroutine 试图在通道中发布值,但由于没有接收器,它面临死锁情况。

消除了第一个示例中的死锁,请参阅链接:https ://play.golang.org/p/6RuQQwC9JkA

但是,如果主例程退出,所有关联的 goroutine 将被自动杀死。这就是为什么我们可以看到该值是任意打印的。

删除了第二个示例中的死锁,请参阅链接:https ://play.golang.org/p/yUmc_jjZMgV

sync.WaitGroup 正在使主 goroutine 等待,这就是为什么我们可以在每次程序执行时观察到输出。


查看完整回答
反对 回复 2023-08-14
  • 2 回答
  • 0 关注
  • 82 浏览
慕课专栏
更多

添加回答

举报

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