liveBook · Manning

Deadlock

As explained in Learn Go with Pocket-Sized Projects

A deadlock is a concurrency failure in which execution cannot make progress because goroutines remain blocked waiting for events that cannot occur.

A deadlock is a concurrency failure in which goroutines cannot make progress because they wait for events that cannot occur. In Go, sending to an unbuffered channel blocks until another goroutine receives; a listener that waits forever after all publishers stop eventually produces a deadlock error. section 9.3.4

A concurrent maze solver can deadlock in several closely related ways:

  • Send the initial path before starting a receiver on an unbuffered channel; the send blocks immediately. section 9.3.4
  • Keep listening after every worker has stopped publishing; Go reports that all goroutines are asleep. section 9.3.4
  • Stop the listener when one worker finds the solution while other explorers still publish; their sends eventually fill the channel and block. section 9.4
  • Send one quit message without stopping every explorer; remaining workers can still attempt blocking sends to a channel nobody reads. section 9.5.2

Prevent the failure by coordinating shutdown. A quit channel gives workers a shared stop signal; closing it broadcasts completion because reads from a closed channel always return. Workers should check quit before exploring and select between quitting and publishing, while a WaitGroup lets the listener wait for active explorers. section 9.5.2

Stopping maze exploration safely

When a worker finds the treasure, record the solution under a mutex and signal shutdown. Each explorer then selects between receiving from quit and sending its next branch: a closed quit channel lets it return instead of publishing work that no goroutine will consume. section 9.5.2

Listing 9.28 explore.go: Exploring only if the treasure isn’t found
func (s *Solver) explore(pathToBranch *path) {
    // ...

    for _, candidate := range candidates[1:] {
        branch := &path{previousStep: pathToBranch, at: candidate}
        select {
        // s.quit returns a zero value only when the channel was closed    
        case <-s.quit:
            log.Printf("I'm an unlucky branch, someone else found the treasure, I give up at position %v.", pos)
            return
        case s.pathsToExplore <- branch:
            // continue execution after the select block
        }
    }

Inspect the worker loop that stops exploring when the quit channel closes and otherwise publishes the next branch. section 9.5.2

How Learn Go with Pocket-Sized Projects covers it

How deep it goes
Explains blocking sends, channel capacity, shutdown races, closed-channel broadcasts, cancellation selects, and worker waiting.
Examples
Uses a concurrent maze solver, blocked path publication, treasure discovery, quit signaling, and worker cancellation.
What it assumes
Assumes familiarity with Go goroutines and channels while demonstrating buffering, channel closure, select, mutexes, and WaitGroup coordination.
What you can do after
Diagnose deadlocks by tracing blocked channel operations and apply coordinated cancellation to stop workers safely.
How it connects
Connects goroutines with channels, buffered channels, select, mutexes, and WaitGroup-based coordination.

Also explained in

Compare how 15 books explain deadlock →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Learn Go with Pocket-Sized Projects. The section links point to the parts of the book it draws on.