liveBook · Manning

Deadlock

As explained in Learn Concurrent Programming with Go

A deadlock is a state in which two or more executions wait indefinitely for conditions that can be satisfied only by one another.

A deadlock occurs when concurrent executions block indefinitely while waiting for one another to release exclusive resources. It can appear only after a program has run normally for some time, making it difficult to identify and debug. section 11.0

Two locks, opposite order

Two goroutines can deadlock when each holds one mutex and waits for the other: red acquires lock1 then lock2, while blue acquires lock2 then lock1. Output stops when each reaches its second lock request. Because execution order is nondeterministic, the same program may not deadlock on every run; delays between lock acquisitions increase the chance. section 11.1

Figure 11.1 Two goroutines competing for two exclusive resources

Trace each goroutine’s held lock and requested lock. A circular wait explains why neither execution can continue. section 11.1

The same pattern can halt a ledger: transfers lock a source account and then a target account, so several goroutines may form a cycle of held and requested account locks. That cycle can also block unrelated work that needs one of the locked accounts. section 11.1.2

Address deadlocks through three broad approaches:

  • Detect them after they occur. section 11.2
  • Avoid unsafe allocations by scheduling resource requests so every execution can still reach completion. section 11.2.2
  • Prevent deadlock scenarios through program design, such as coordinating acquisition of all required accounts before a transfer proceeds. section 11.2 section 11.2.2

A safe state has some schedule in which all executions can complete even if each requests its maximum resources. The banker’s algorithm decides whether granting a request preserves that safety, requiring known maximum requests, current holdings, and available resources. section 11.2.2

Arbitrating ledger transfers

An arbitrator tracks whether accounts are in use. A goroutine waits on a condition variable until every requested account is free, marks them all in use together, performs the transfer, then frees them and broadcasts to waiting goroutines. This prevents a transfer from holding one account while waiting for another. section 11.2.2

Practice by changing code that locks multiple player records concurrently, or by using select to avoid circular waits between channel operations. section 11.4

How Learn Concurrent Programming with Go covers it

How deep it goes
Defines deadlock, traces mutex and ledger cycles, and develops safe allocation plus a condition-variable arbitrator.
Examples
Uses opposing mutex acquisitions, bank-account transfers, rail crossings, player scores, and channel send-receive exercises.
What it assumes
Assumes some Go or similar C-style language experience while introducing concurrency concepts from a gentle starting point.
What you can do after
Apply it via sync.Cond arbitration to coordinate multi-account transfers without holding one account while awaiting another.
How it connects
Connects deadlocks with goroutines, mutexes, resource allocation graphs, condition variables, channels, and select.

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 Concurrent Programming with Go. The section links point to the parts of the book it draws on.