首页  >  文章  >  后端开发  >  为什么 Go 的 select 用例中的链式通道操作会导致死锁?

为什么 Go 的 select 用例中的链式通道操作会导致死锁?

Linda Hamilton
Linda Hamilton原创
2024-11-25 05:48:10960浏览

Why Do Chained Channel Operations in Go's `select` Case Cause Deadlocks?

单个 select 案例中的链式通道操作:解码行为

为了设计并发和异步程序,Go 的 select 构造提供了多路复用通道的强大工具。然而,在单个 select case 中组合多个操作时,经常会遇到意想不到的结果。

考虑以下场景:两个通道 A 和 B 以不同的时间间隔发送消息(A 为 10 毫秒,A 为 1 秒) B)。我们使用 select 来监听两个通道,并将接收到的值转发到扇入通道。

func main() {
    ch := fanIn(talk("A", 10), talk("B", 1000))

    for i := 0; i < 10; i++ {
        fmt.Printf("%q\n", <-ch)
    }
    fmt.Printf("Done\n")
}

预期结果是:

"A 0"
"B 0"
"A 1"
"A 2"
"A 3"
"A 4"
"B 1"
"B 2"
"B 3"
"B 4"
Done

但是,当我们修改 select 时使用链式通道操作的情况:

select {
    case ch <- <-input1:
    case ch <- <-input2:
}

我们观察到一个特殊的情况行为:

"B 0"
"A 1"
"B 2"
"A 3"
"A 4"
fatal error: all goroutines are asleep - deadlock!

幕后

理解此行为的关键在于选择案例中通道操作的非阻塞性质。在典型的选择情况下,只有一个通道操作(读或写)可以是非阻塞的。

当我们使用链式通道操作时,我们实际上是在单个情况下尝试多个通道操作。第一个操作始终是阻塞的,而后续操作是非阻塞的。

在我们修改后的代码中,第一个操作会阻塞以从 input1 接收值。收到值后,它会尝试将其非阻塞地写入 ch 通道。但是,如果 ch 通道的接收者还没有准备好接受该值,则写入操作将失败。

连锁反应

失败的写入操作不会停止选择案例。相反,它转向第二种情况,这是现在唯一可行的情况。这会导致潜在的死锁情况。

随着时间的推移,会收到来自两个通道的多个值,但由于写入失败而不会转发到扇入通道。因此,扇入通道最终会变空,从而导致死锁,因为无法接收更多值。

解决问题

为了避免此问题,它对于确保选择案例中的通道操作串行执行至关重要。这可以通过使用临时变量来存储接收到的值,然后在 select case 之外作为单独的语句执行写入操作来实现。

var msg string
select {
    case msg = <-input1:
    case msg = <-input2:
}

ch <- msg

以上是为什么 Go 的 select 用例中的链式通道操作会导致死锁?的详细内容。更多信息请关注PHP中文网其他相关文章!

声明:
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn