Skip to main content

General Concepts

Deadlock in Go

What is Deadlock?

Deadlock is a state in which a task starts waiting for something that will not happen. As a result, it stops progressing.

Operating systems use multi-processing and multi-tasking for process completion, task completion, and to speed up work. When the two processes run concurrently and block each other’s paths, this condition is called deadlock.

In other words, deadlock is a condition when a process cannot be completed because it doesn’t get the resources it requires.

The conditions to declare a deadlock state are as follows:

  • Only one process can use the resource at a time, resulting in exclusive control of the resources.
  • Both processes hold the resources and wait for another process to leave hold of their resource.
  • Until the process completes its execution, it doesn’t give up on resources.
  • Each process in the chain holds a resource requested by another.

Example

Suppose two processes A and B are running simultaneously.

process A {
     update variable x
     update variable y
}

process B {
     update variable y
     update variable x
}

Since both processes are executing, process A will acquire a lock on variable x, and process B will acquire a lock on variable y.

Deadlock

Since process B holds the lock on resource y, process A can’t proceed further to acquire a lock on variable y. The same thing happens with process A as well. So, the processes will be in a deadlock state. Neither can move further, and they’ll wait indefinitely for each other to release the resources.

Deadlock in Go

Let's see some code examples through which Deadlock state can be implemented in Go.

We will use the concept of Channels and Goroutine to implement Deadlock state. Though, we have not discussed them so far but let's focus on underlying concept.

Deadlock Due to Sender

The basic concept is that a channel can be a buffered channel or an unbuffered channel (with zero capacity). In this example we will use a unbuffered channel (channel with zero buffer capacity).

In case, we pass the value into unbuffered channel, and there is no corresponding goroutine ready to receive the value, it’ll throw a deadlock error.

💡
Unbuffered channels require both a sender and a receiver to be ready at the same time for communication to happen.

Code

Create a deadlock-due-to-sender.go file in your current directory.

package main

func DeadlockDueToSenderOnly() {
	channel := make(chan int)
	channel <- 1
}

Now, in your main.go file, call DeadlockDueToSenderOnly() method.

package main

func main() {
	DeadlockDueToSenderOnly()
}

Now run go run .to see the output.

fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan send]:
main.DeadlockDueToSenderOnly(...)
        /Users/aakashverma/Documents/Learning/Go/go-concurrency/deadlock/deadlock-due-to-sender.go:5
main.main()
        /Users/aakashverma/Documents/Learning/Go/go-concurrency/deadlock/main.go:4 +0x34
exit status 2

Since there is no other Goroutine to accept the sent value (no other Goroutine is available for the system to make progress), this situation will crash the program and declare it a deadlock.

You will definitely get it when we will talk about channel in detail.

Deadlock Due to Receiver

If we create a channel and start receiving values and there's no other Goroutine attempting to send a value into the channel. This scenario will create a deadlock.

Code

Create a deadlock-due-to-receiver.go file in your current directory.

package main

func DeadlockDueToReceiverOnly() {
	channel := make(chan int)
	<-channel
}

Now, in your main.go file, call DeadlockDueToReceiverOnly() method.

package main

func main() {
	DeadlockDueToReceiverOnly()
}

Now run go run .to see the output.

fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan receive]:
main.DeadlockDueToReceiverOnly(...)
        /Users/aakashverma/Documents/Learning/Go/go-concurrency/deadlock/deadlock-due-to-receiver.go:4
main.main()
        /Users/aakashverma/Documents/Learning/Go/go-concurrency/deadlock/main.go:5 +0x30
exit status 2

How is Deadlock in OS and Go different?

Waiting for resources in the OS causes a deadlock, whereas, in Golang, we don’t have to worry about the resources and allocation.

So how is deadlock in OS and Golang similar? The basic definition of deadlock is a state in which progress is impossible. In Golang, there’s no other goroutine to accept the value, and, therefore, it will never progress and wait in the blocked state forever. Hence, it’s a deadlock.

If all of the goroutines in our program are in a blocked state, then the go runtime scheduler notices that it can do nothing and reports that the system is in a deadlock.

Before reporting the deadlock error, the go runtime scheduler will check if any of the goroutines are ready to run or waiting on something. If so, they will start their execution. Otherwise, the scheduler will report the deadlock error.