[Golang] Experiment with Deadlock

Deadlock happens when there are 2 or more resources, and in order to get the resources, we need to acquire lock first, then the order of acquiring lock is different. Eg: Process A wants to get value of variable 1 and then variable 2, meanwhile Process B wants to get value of variable 2, then variable 1. Before accessing the variable, each process will acquire the lock.

A picture is worth a thousand words, right? Here it is:

Deadlock Image

Here is the sample code to trigger deadlock in go. This example is inspired by Concurrency in Go book.

package main

import (
	"log"
	"sync"
	"time"
)

type Something struct {
	val  int
	lock sync.Mutex
}

func main() {
	a := Something{
		val: 12,
	}

	b := Something{
		val: 13,
	}

	var wg sync.WaitGroup

	sum := func(a, b *Something) {
		defer wg.Done()

		a.lock.Lock()
		defer a.lock.Unlock()

		time.Sleep(1 * time.Second)

		b.lock.Lock()
		defer b.lock.Unlock()

		log.Printf("sum: %d\n", a.val+b.val)
	}

	wg.Add(2)

	go sum(&a, &b)
	go sum(&b, &a)

	wg.Wait()

	log.Printf("done")
}

Result:

dickynovanto@dickys-macbook % go run .
fatal error: all goroutines are asleep - deadlock!

Using time.Sleep above makes the deadlock easily simulated. However, I can show you that the above scenario can happen as well without using time.Sleep. We just need to run the procedures quite a lot of time and with “luck”, the deadlock will happen.

package main

import (
	"log"
	"sync"
)

type Something struct {
	val  int
	lock sync.Mutex
}

func main() {
	a := Something{
		val: 12,
	}

	b := Something{
		val: 13,
	}

	var wg sync.WaitGroup

	sum := func(a, b *Something) {
		defer wg.Done()

		a.lock.Lock()
		defer a.lock.Unlock()

		// log.Printf("sleeping")
		// time.Sleep(2 * time.Second)
		// log.Printf("after sleeping")

		b.lock.Lock()
		defer b.lock.Unlock()

		log.Printf("sum: %d\n", a.val+b.val)
	}

	for i := 0; i < 10000; i++ {
		log.Printf("i: %v", i)
		wg.Add(2)

		go sum(&a, &b)
		go sum(&b, &a)

		wg.Wait()
	}

	log.Printf("done")
}

The triggering of deadlock is not deterministic in this case, sometimes, only after thousands of iteration, the deadlock can be triggered. One of the result:

2022/05/23 08:46:00 i: 0
...
2022/05/23 08:46:00 sum: 25
2022/05/23 08:46:00 i: 5461
fatal error: all goroutines are asleep - deadlock!