Elliot Chance
Elliot Chance

Reputation: 5736

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?

Upvotes: 2

Views: 163

Answers (3)

tomasz
tomasz

Reputation: 13052

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.

Upvotes: 8

HectorJ
HectorJ

Reputation: 6324

goroutine 1 [chan send]:
goroutine 5 [chan send]:
goroutine 6 [chan send]:

This tells it all: all your goroutines are blocked trying to send on a channel with no one to receive on the other end.

So your first goroutine blocks on chan2 <- true, your second blocks on chan1 <- true and your main goroutine blocks on its own chan1 <- true.

As to why it does two "full ping-pings" like you say, it depends on scheduling and from which sender <-chan1 decides to receive first.

On my computer, I get more and it varies each time I run it:

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

Upvotes: 1

mostruash
mostruash

Reputation: 4189

It looks complicated but the answer is easy.

It'll deadlock when:

  • First routine is trying to write to chan2
  • Second route is trying to write to chan1.
  • Main is trying to write to chan1.

How can that happen? Example:

  • Main writes chan1. Blocks on another write.
  • Routine 1: chan1 receives from Main. Prints. Blocks on write chan2.
  • Routine 2: chan2 receives. Prints. Blocks on write chan1.
  • Routine 1: chan1 receives from Routine 2. Prints. Blocks on write chan2.
  • Routine 2: chan2 receives. Prints. Blocks on write chan1.
  • Main writes chan1. Blocks on another write.
  • Routine 1: chan1 receives from Main. Prints. Blocks on write chan2.
  • Main writes chan1. Blocks on another write.

Currently all routines are blocked. i.e.:

Routine 1 cannot write to chan2 because Routine 2 is not receiving but is actually blocked trying to write to chan1. But no one is listening on chan1.

As @HectorJ said, it all depends on the scheduler. But in this setup, a deadlock is inevitable.

Upvotes: 1

Related Questions