Master

A small concurrent tool

Put the pieces together once and the tool is reusable. A queue, a worker count, a limit per wait, and a report of what arrived is the whole shape.

What you will see

end

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 finished tool.

1

Queue drained.

import queue q = queue.Queue() for n in range(4): q.put(n) while not q.empty(): q.get() print(q.qsize())

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

What it prints
0
2

Parts counted.

parts = ["queue", "workers", "limits", "report"] print(len(parts))

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

What it prints
4
3

Tally exact.

import threading lock = threading.Lock() arrived = 0 def note(): global arrived with lock: arrived = arrived + 1 for _ in range(3): note() print(arrived)

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

What it prints
3
4

Sentinel ends.

import queue q = queue.Queue() q.put("job") q.put(None) item = q.get() while item is not None: item = q.get() print("end")

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

What it prints
end

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

Work goes on a queue, workers drain it, each wait has a limit, a tally is kept under a lock, and a report states what arrived.
1 put work on the queue
↓
2 workers drain it
↓
3 each wait has a limit
↓
4 report what arrived
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 queue sets nothing on screen
q = queue.Queue() sets nothing on screen
for n in range(4): sets nothing on screen
q.put(n) sets nothing on screen
while not q.empty(): sets nothing on screen
q.get() sets nothing on screen
print(q.qsize()) 0
Why this works
Four parts, four questions

The queue asks what the work is, the workers ask how many, the limits ask how long, and the report asks what arrived. A stall answers one of those.

One shared cell left

A tool with one shared tally is nearly race free. Every extra shared cell is another window to argue about.

Examples

The worker pool

import queue
import threading

q = queue.Queue()
for n in range(4):
    q.put(n)

def worker():
    while not q.empty():
        q.get()

workers = [threading.Thread(target=worker) for _ in range(2)]
for w in workers:
    w.start()
for w in workers:
    w.join()
print(q.qsize())

Two workers drain one queue. The worker count is a dial, not a rewrite.

A limit per item

import threading

done = 0
for _ in range(3):
    done = done + 1
print(done == 3)

Each item gets its own limit, so one slow item cannot hold the whole batch past the run budget.

A report of parts

arrived = [1, 1, 0]
print(sum(arrived), len(arrived))

The report carries both numbers. A count of arrived items beside the total is what makes a partial run visible.

A lock for the tally

import threading

lock = threading.Lock()
arrived = 0

def note():
    global arrived
    with lock:
        arrived = arrived + 1

for _ in range(3):
    note()
print(arrived)

The tally is the one shared cell left in the tool. A lock around it is the whole cost.

A stop signal

import queue

q = queue.Queue()
q.put("job")
q.put(None)
item = q.get()
while item is not None:
    item = q.get()
print("end")

One sentinel per worker ends the batch. A worker that never sees one never ends.

Name the parts

parts = ["queue", "workers", "limits", "report"]
print(len(parts))

Four named parts make the tool. Each one is a question a reviewer can ask, which is why the shape survives a change.

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.

← Designing shared state
Previous Lesson
Designing shared state