使用select时,如何避免死锁和静默错误?

huangapple go评论73阅读模式
英文:

How do I avoid deadlocks and silent errors when using 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,两个消息都会被接收,但我会得到fatal error: all goroutines are asleep - deadlock!的错误。

通过预读和在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 &lt; 2; i++ {
        select {
        case msg1 := &lt;-c1:
            fmt.Println(&quot;received&quot;, msg1)
        case msg2 := &lt;-c2:
            fmt.Println(&quot;received&quot;, 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?

答案1

得分: 4

从概念上讲,你可以通过正确设计软件来减轻这个问题。如果你有两个通道,并且每个通道最多只会接收一条消息,那就不要尝试从它们那里读取三次。这与试图将三个项目放入一个两个元素的数组中或者试图除以0的情况没有区别。在所有这些情况下,编程语言都提供了发现和恢复错误的方法,但如果你实际上产生了这些错误,那就表示存在逻辑或设计缺陷。

你需要确保你的通道在读取和写入方面保持平衡,并且在发送端没有其他要发送的内容时关闭通道,这样接收方就可以停止等待不会到达的消息。否则,最终会有某些东西被卡在等待中,或者缓冲区中的消息被忽略。

在这个非常特殊的情况下,如果你想从两个通道中读取,但只有在有消息准备好时才读取,你可以添加一个default情况,如果没有通道准备好进行读取,就会调用该情况。但这只适用于通道还没有准备好但最终会准备好的情况。提供一个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 a default 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.

huangapple
  • 本文由 发表于 2022年3月10日 00:05:50
  • 转载请务必保留本文链接:https://go.coder-hub.com/71412386.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定