Sobes.tech
Junior

What is a deadlock when working with goroutines?

sobes.tech AI

Answer from AI

Deadlock (stalemate) is a situation where two or more goroutines are blocked, waiting for each other, and none of them can continue execution. It most often occurs when working with mutexes or channels.

Examples of deadlock:

  1. Locking mutexes in different orders:

    // Example of deadlock with mutexes
    var mu1 sync.Mutex
    var mu2 sync.Mutex
    
    func goroutine1() {
        mu1.Lock() // Locks mu1
        time.Sleep(100 * time.Millisecond) // Waits a bit
        mu2.Lock() // Tries to lock mu2 (occupied by goroutine2)
        fmt.Println("goroutine1 acquired mu2")
        mu2.Unlock()
        mu1.Unlock()
    }
    
    func goroutine2() {
        mu2.Lock() // Locks mu2
        time.Sleep(100 * time.Millisecond) // Waits a bit
        mu1.Lock() // Tries to lock mu1 (occupied by goroutine1)
        fmt.Println("goroutine2 acquired mu1")
        mu1.Unlock()
        mu2.Unlock()
    }
    

    Goroutine1 has locked mu1 and is waiting for mu2. Goroutine2 has locked mu2 and is waiting for mu1. Both goroutines are blocked.

  2. Sending to an unbuffered channel without a receiver:

    // Example of deadlock with channel
    func main() {
        ch := make(chan int)
        //go func() { // If no goroutine is started for reading, deadlock occurs
        //
        //<-ch
        //}()
        ch <- 1 // Sending to a channel without an active receiver
        fmt.Println("Sent to channel") // This line will not be reached
    }
    

    Sending to an unbuffered channel blocks until another goroutine starts reading from it. If there is no receiver, deadlock occurs.

  3. Reading from a channel without a sender (after closing or empty buffered):

    // Example of deadlock when reading from a channel
    func main() {
        ch := make(chan int, 1) // Buffered channel with capacity 1
        ch <- 1 // Sent 1
        close(ch) // Closed the channel
        <-ch // Read 1
        <-ch // Attempt to read again from a closed and empty channel - deadlock
    }
    

    Reading from a closed non-empty channel will succeed. Reading from a closed and empty channel or from an open but empty unbuffered channel without a sender will block.

The Go runtime can detect certain types of deadlocks (for example, when all blocked goroutines are waiting to receive or send on a single goroutine), and in such cases, the program will terminate with the message "fatal error: all goroutines are asleep - deadlock!". However, not all deadlocks can be automatically detected.

To prevent deadlocks, it is important to:

  • Maintain a consistent order of mutex acquisition.
  • Ensure that for every send to an unbuffered channel, there is a corresponding receive, and vice versa.
  • Use select with default case or timeouts when working with channels if necessary.
  • Carefully manage the lifetime of goroutines and closing of channels.