Search code examples
godeadlock

Why does this cause a deadlock in Go?


This is not a question about how to better write this. It's a question specifically about why Go is causing a deadlock in this scenario.

package main

import "fmt"

func main() {
    chan1 := make(chan bool)
    chan2 := make(chan bool)

    go func() {
        for {
            <-chan1
            fmt.Printf("chan1\n")
            chan2 <- true
        }
    }()

    go func() {
        for {
            <-chan2
            fmt.Printf("chan2\n")
            chan1 <- true
        }
    }()

    for {
        chan1 <- true
    }
}

Outputs:

chan1
chan2
chan1
chan2
chan1
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan send]:
goroutine 5 [chan send]:
goroutine 6 [chan send]:
exit status 2

Why does this not cause an infinite loop? How come it does two full "ping-pings" (instead of just one) before giving up?


Solution

  • From the runtime perspective you get a deadlock because all routines try to send onto a channel and there's no routine waiting to receive anything.

    But why is it happening? I will give you a story as I like visualising what my routines are doing when I encounter a deadlock.

    You have two players (routines) and one ball (true value). Every player waits for a ball and once they get it they pass it back to the other player (through a channel). This is what your two routines are really doing and this would indeed produce an infinite loop.

    The problem is the third player introduced in your main loop. He's hiding behind the second player and once he sees the first player has empty hands, he throws another ball at him. So we end up with both players holding a ball, couldn't pass it to another player because the other one has (the first) ball in his hands already. The hidden, evil player is also trying to pass yet another ball. Everyone is confused, because there're three balls, three players and no empty hands.

    In other words, you have introduced the third player who is breaking the game. He should be an arbiter passing the very first ball at the beginning of the game, watching it, but stop producing balls! It means, instead of having a loop in you main routine, there should be simply chan1 <- true (and some condition to wait, so we don't exit the program).

    If you enable logging in the loop of main routine, you will see the deadlock occurs always on the third iteration. The number of times the other routines are executed depends on the scheduler. Bringing back the story: first iteration is a kick-off of the first ball; next iteration is a mysterious second ball, but this can be handled. The third iteration is a deadlock – it brings to life the third ball which can't be handled by anybody.