Go: Common Deadlocks I

Whether pro or amateur you've probably seen this:

fatal error: all goroutines are asleep - deadlock!

What is this and why does it happen? All you need to know is in the error message actually. It means that all your goroutines are asleep which in other term means you have no more code running. No code is running anymore. In other programming languages and/or environments/runtime systems your program wouldn't be terminated and would literally sleep forever until you terminate. The go runtime system however terminates the program if it recognizes that all goroutines have been put asleep and displays the above fatal error message. I really like this decision because if everything in your program is asleep then what's the point of keeping it running? Also, it means the programmer made a programming mistake. This isn't an error that sometimes happens and sometimes doesn't (although in reality that might be the case) but it should be treated as a fatal programming error even if it doesn't always occur. If it just occurs once it means you have a potentially serious bug in your code. You're making yourself vulnerable to Denial Of Service attacks. Don't ignore such errors. Fix them! As soon as possible! It's a fatal bug!

A deadlock more generally refers to when two threads/fibres/goroutines are mutually waiting on something. A common scenario is for example for one thread #A to have acquired resource #1 and and thread #B to have acquired resource #2 and now thread #A wants to also acquire resource #2 and thread #B also wants to acquired resource 1. In this case you run into a deadlock because both will wait forever until the resources become free.

Let's look at one very simple program with one goroutine and a deadlock:

package main

import (
	"fmt"
	"sync"
)

func main() {
	var wg sync.WaitGroup
	wg.Add(1)
	wg.Wait()
	fmt.Println("exit")
}

This program will terminate with the aforementioned fatal error message. The reason is that wg.Wait() causes the only active goroutine (main) to wait for somebody to call wg.Done() which is never going to happen. There's nothing in that code that would ever call that. Thus it is put asleep (also refered to as blocked). Since now all goroutines are asleep the go runtime system will terminate the program. But this is of course an artificially made up unrealistic example. Let's have a look at something more real.

package main

import (
	"fmt"
	"sync"
)

func main() {
	var wg sync.WaitGroup

	ch := make(chan int)

	wg.Add(2)

	go func() {

		for v := range ch {
			fmt.Println(v)
		}
		wg.Done()
	}()

	go func() {
		for v := range ch {
			fmt.Println(v)
		}
		wg.Done()
	}()

	for i := 0; i < 10; i++ {
		ch <- i
	}

	wg.Wait()
}

Why does this program deadlock? Think about it. The solution is here.

Let's look at another example.

package main

import (
	"fmt"
	"sync"
)

func main() {
	var wg sync.WaitGroup

	ch := make(chan int)

	wg.Add(1)
	
	for v := range ch {
		fmt.Println(v)
	}

	go func() {
		for i := 0; i < 10; i++ {
			ch <- i
		}
		close(ch)
		wg.Done()
	}()

	wg.Wait()
}

Why is this deadlocking? Think about it. The solution is here.