猿问

如何修复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

我哪里出错了,如何解决?


森林海
浏览 156回答 2
2回答

哆啦的时光机

您的main()函数尝试在所有通道上发送,并且只有一次尝试在单独的并发 goroutine 中从这些通道中读取。这是否成功取决于 goroutine 调度程序。如果非阻塞接收f2()比发送更早安排main(),那么后面的发送main()将永远阻塞(没有人会再次尝试接收ch2)。摆脱死锁的一种方法是使用接收操作而不是非阻塞接收(在Go Playground上尝试):func f1() {&nbsp; &nbsp; <-ch1&nbsp; &nbsp; fmt.Println("ch1")}func f2() {&nbsp; &nbsp; <-ch2&nbsp; &nbsp; 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.WaitGroupfunc f1() {&nbsp; &nbsp; defer wg.Done()&nbsp; &nbsp; <-ch1&nbsp; &nbsp; fmt.Println("ch1")}func f2() {&nbsp; &nbsp; defer wg.Done()&nbsp; &nbsp; <-ch2&nbsp; &nbsp; fmt.Println("ch2")}func main() {&nbsp; &nbsp; wg.Add(1)&nbsp; &nbsp; go f1()&nbsp; &nbsp; wg.Add(1)&nbsp; &nbsp; go f2()&nbsp; &nbsp; ch1 <- 1&nbsp; &nbsp; ch2 <- 2&nbsp; &nbsp; 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()

噜噜哒

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

相关分类

Go
我要回答