单个 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中文网其他相关文章!