使用 select 时如何避免死锁和无提示错误?
我正在通过示例学习 Go。我刚刚实现了一个 select 来等待多个通道,如下所示:
for i := 0; i < 2; i++ {
select {
case msg1 := <-c1:
fmt.Println("received", msg1)
case msg2 := <-c2:
fmt.Println("received", msg2)
}
}
通过一些实验,我发现我可以天真地引入运行时错误,如下所示:
如果我将 i 减少到 1,则会收到第一条消息,但是第二个默默地丢失了(没有迹象表明我无意中忽略了它)。
如果我将 i 增加到 3,两条消息都会收到,但我会收到
致命错误:所有 goroutine 都在睡眠 - 死锁!
提前阅读并在 StackOverflow 上搜索该错误消息我可以看到WaitGroups 解决了这些类型的问题。但它们似乎不适用于 select
,所以我觉得我一定错过了一些东西。
是否有一种语言结构(例如 if/then/else
)或软件模式可以用来防止或减轻现实代码中的这些错误?
I am learning Go by example. I've just implemented a select to await multiple channels as follows:
for i := 0; i < 2; i++ {
select {
case msg1 := <-c1:
fmt.Println("received", msg1)
case msg2 := <-c2:
fmt.Println("received", msg2)
}
}
With a little experimentation I've found that I can naively introduce runtime errors as follows:
If I reduce i to 1, the first message is received, but the second is silently lost (there is no indication that I unwittingly ignored it).
If I increase i to 3, both messages are received but I get
fatal error: all goroutines are asleep - deadlock!
Reading ahead and searching for that error message on StackOverflow I can see that WaitGroups account for these types of issues. But they don't seem to apply to select
, so I feel like I must be missing something.
Is there a language construct (like if/then/else
) or software pattern that I can use to prevent or mitigate against these errors in real-world code?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

从概念上讲,您可以通过正确设计软件来缓解这种情况。如果您有两个频道,并且每个频道最多接收一条消息,请不要尝试从其中读取 3 次。这与尝试将三个项目放入一个二元素数组中,或者尝试除除数为 0 的两个数字没有什么不同。在所有这些情况下,语言都提供了发现错误并从错误中恢复的方法,但如果您实际上正在生成错误这些错误,表明存在逻辑或设计缺陷。
您需要确保您的通道具有均衡的读写数量,并且发送端在没有其他内容可发送时关闭通道,以便接收方可以停止等待不会到来的消息。否则,您最终会遇到一些等待的问题,或者缓冲区中的消息被忽略。
在这个非常具体的情况下,如果您想从两个通道读取但仅当消息准备就绪时,您可以添加一个
default
情况,如果没有通道可供读取,则将调用该情况,但这是为了您的渠道尚未准备就绪但最终会准备就绪的情况。提供默认
并不是一个很好的解决方案来掩盖错误,因为通道永远不会准备好,而您仍然试图从中读取数据;这表明存在需要修复的逻辑级缺陷。Conceptually you mitigate against this by designing software correctly. If you have two channels, and each channel will receive at most one message, don't try to read from them 3 times. This is no different than trying to put three items in a two element array, or trying to divide two numbers where the divisor is 0. In all these cases languages offer ways of discovering and recovering from the error, but if you're actually producing these errors, it indicates a logic or design flaw.
You need to make sure that your channels have a balanced number of reads and writes, and that the sending end closes the channel when it has nothing else to send so receivers can stop waiting for messages that won't come. Otherwise you'll eventually have something stuck waiting, or messages in a buffer that are ignored.
In this very specific case, if you want to read from both channels but only if a message is ready, you can add a
default
case which will be invoked if no channel is ready for reading, but that's for situations where your channels are not ready yet but will eventually become ready. Providing adefault
is not a good solution to cover over bugs where channels will never become ready yet you're still trying to read from them; that indicates a logic-level flaw that needs to be fixed.