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

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

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

Go
森林海 2022-04-26 14:13:43
我有这段 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 回答

?
哆啦的时光机

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

您的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 完成。因此,您可能无法在控制台上看到ch1和打印。ch2有关详细信息,请参阅Go 中的 goroutine 没有输出。


如果您的意图是在您的应用程序存在之前等待所有 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()

}

在此处查看更多示例:解决 goroutines 死锁;并防止 main() 函数在 goroutine 在 Golang 中完成之前终止。


另一种选择是为通道提供 1 的缓冲区,因此main()可以在没有接收器准备好从通道接收的情况下在通道上发送 1 值(在Go Playground上试试这个):


var ch1 = make(chan int, 1)

var ch2 = make(chan int, 1)

有了这个,可以在没有and的main()情况下继续,所以再一次,不能保证你会看到任何打印出来的东西。f1()f2()


查看完整回答
反对 回复 2022-04-26
?
噜噜哒

TA贡献1784条经验 获得超7个赞

此处使用的通道是无缓冲通道。

  • 当 main goroutines 启动时,它会创建两个新的 goroutine f1 和 f2。

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

  • 理想情况下,channels 会先发布值,然后通过 goroutine 接收

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

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

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

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

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


查看完整回答
反对 回复 2022-04-26
  • 2 回答
  • 0 关注
  • 154 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号