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

Tro*_*yvs 2 locking channel go

我有这段 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
}
Run Code Online (Sandbox Code Playgroud)

它总是打印这样的东西:

default
ch1
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan send]:
main.main()
    /tmp/sandbox970110849/prog.go:22 +0xa0
Run Code Online (Sandbox Code Playgroud)

此外,我试过这个:

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()
}
Run Code Online (Sandbox Code Playgroud)

这次的错误更多:

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
Run Code Online (Sandbox Code Playgroud)

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

icz*_*cza 5

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

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

func f1() {
    <-ch1
    fmt.Println("ch1")
}
func f2() {
    <-ch2
    fmt.Println("ch2")
}
Run Code Online (Sandbox Code Playgroud)

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

请注意,当main()返回时,应用程序结束,它不会等待非主 goroutines 完成。因此,您可能不会在控制台上看到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()
}
Run Code Online (Sandbox Code Playgroud)

在此处查看更多示例:Solving goroutines deadlock;并防止 main() 函数在 Golang 中的 goroutines 完成之前终止。

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

var ch1 = make(chan int, 1)
var ch2 = make(chan int, 1)
Run Code Online (Sandbox Code Playgroud)

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