Advanced

Two locks, two orders

A deadlock is two holders each waiting for what the other holds. Nothing can move, and the program looks alive, so the symptom is a hang and not an error.

What you will see

left right

About 2 min of reading

Quick check

Before this new lesson, one small question about the lesson you just finished. Recall it, do not look it up.

Try It

Check the lock rules.

1

Order taken.

import threading left = threading.Lock() right = threading.Lock() with left, right: print("both")

Before you run it, say out loud what it will print. Then open the answer.

What it prints
both
2

Acquire works.

import threading lock = threading.Lock() got = lock.acquire(0.01) lock.release() print(bool(got))

Before you run it, say out loud what it will print. Then open the answer.

What it prints
True
3

One lock.

import threading one = threading.Lock() with one: print("one")

Before you run it, say out loud what it will print. Then open the answer.

What it prints
one
4

Levels ranked.

rank = {"left": 1, "right": 2} order = sorted(rank, key=lambda k: rank[k]) print(" ".join(order))

Before you run it, say out loud what it will print. Then open the answer.

What it prints
left right

Put it in order

Before you write anything, put these lines back in the right order. Click a line to move it up one step. This support step is easier than writing code from scratch, and it is here to make the last example stick.

Data Flow

A holder takes the first lock, then the second, a holder in the other order waits forever, and a timeout turns that wait into a value.
1 take the first lock
↓
2 take the second lock
↓
3 the other order waits
↓
4 a timeout reports it
Line by line

Read the first example one line at a time. A line that prints shows what appears on screen; the rest set things up quietly.

import threading sets nothing on screen
left = threading.Lock() sets nothing on screen
right = threading.Lock() sets nothing on screen
with left, right: sets nothing on screen
print("both") both
Why this works
Order is the rule

A deadlock needs a cycle. One stated order for every holder removes the cycle, so the fix is a rule and not a patch.

A hang is not a crash

A deadlocked program uses no CPU and raises nothing. A timeout on acquire or a watchdog is what makes it report.

Examples

The safe order

import threading

left = threading.Lock()
right = threading.Lock()
with left:
    with right:
        print("ok")

Every holder takes the locks in the same order, so a holder can never wait for a lock a slower holder owns.

The other order

import threading

left = threading.Lock()
right = threading.Lock()
with right:
    with left:
        print("ok")

This body alone is fine. Paired with the order above, one holder can wait for the other and both stop.

A timeout shows it

import threading

lock = threading.Lock()
got = lock.acquire(0.01)
lock.release()
print(bool(got))

A timeout on acquire turns a silent wait into a value. A false here names the holder that could not enter.

Fewer locks

import threading

one = threading.Lock()
with one:
    print("one")

One lock for the whole section cannot deadlock with itself. Two locks are only worth taking when they guard different things.

Take them together

import threading

left = threading.Lock()
right = threading.Lock()
with left, right:
    print("both")

One statement takes both, in the order written. The order is stated once and cannot drift between bodies.

A level to rank by

rank = {"left": 1, "right": 2}
order = sorted(rank, key=lambda k: rank[k])
print(" ".join(order))

Give each lock a level and always take them lowest first. That rule is what makes the order mechanical.

Words this lesson uses

Each one is defined here on this site. Open the entry for the full definition and the other words it points at.

← Coordinating two streams
Previous Lesson
Coordinating two streams
Next Lesson
A limit on a wait
A limit on a wait →