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

我有这段Go代码。我需要具备这种能力:在一个地方写入通道,然后在另一个地方读出它们(反之亦然):


package main

import "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

}

它总是像这样打印:


default

ch1

fatal error: all goroutines are asleep - deadlock!


goroutine 1 [chan send]:

main.main()

    /tmp/sandbox970110849/prog.go:22 +0xa0

此外我尝试了这个:


package main

import (

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

}

这次错误更多:


default

ch2

fatal error: all goroutines are asleep - deadlock!


goroutine 1 [semacquire]:

sync.runtime_Semacquire(0x40e028, 0x0)

    /usr/local/go/src/runtime/sema.go:56 +0x40

sync.(*WaitGroup).Wait(0x40e020, 0x14b720)

    /usr/local/go/src/sync/waitgroup.go:130 +0x60

main.main()

    /tmp/sandbox916639182/prog.go:36 +0x100


goroutine 8 [chan send]:

main.w1()

    /tmp/sandbox916639182/prog.go:23 +0x40

created by main.main

    /tmp/sandbox916639182/prog.go:34 +0xc0

我哪里出了问题以及如何解决?


慕妹3146593
浏览 84回答 2
2回答

幕布斯7119047

您的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打印在控制台上。如果您的目的是在您的应用程序存在之前等待所有 goroutine 完成其工作,请使用sync.WaitGroup它(在Go Playground上尝试):var ch1 = make(chan int)var ch2 = make(chan int)var wg sync.WaitGroupfunc 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()

慕少森

这里使用的通道是无缓冲通道。当主协程启动时,它会创建两个新的协程 f1 和 f2。当执行 f1 或 f2 时,它将检查通道中是否有值,否则它将打印默认消息并退出。在理想情况下,通道将首先发布值,然后通过 goroutine 接收它实际情况是 Goroutine 通过打印默认情况退出,但主 Goroutine 试图在通道中发布值,但由于没有接收器,它面临死锁情况。消除了第一个示例中的死锁,请参阅链接:https ://play.golang.org/p/6RuQQwC9JkA但是,如果主例程退出,所有关联的 goroutine 将被自动杀死。这就是为什么我们可以看到该值是任意打印的。删除了第二个示例中的死锁,请参阅链接:https ://play.golang.org/p/yUmc_jjZMgVsync.WaitGroup 正在使主 goroutine 等待,这就是为什么我们可以在每次程序执行时观察到输出。
打开App,查看更多内容
随时随地看视频慕课网APP