Home > Blockchain >  How do I avoid deadlocks and silent errors when using select?
How do I avoid deadlocks and silent errors when using select?

Time:03-10

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 could easily introduce runtime errors:

  • If I reduce i to 1, the first message is received, but the second is lost silently.

  • 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 are one solution to these types of problems. But they don't seem to apply to select.

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?

CodePudding user response:

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.

  • Related