Python Concurrency Quiz: GIL, Threads, Processes and asyncio
Test your Python concurrency with 12 questions and three programs on the GIL, threads and locks, queues, executors, multiprocessing and asyncio gather.
- Course: Python study plan
- Module: Concurrency — threads, processes and asyncio
- Kind: Checkpoint — cleared at 70%
- Reading time: 25 min
- Runtime: CPython 3.11
Checkpoint — Concurrency is the checkpoint that closes the Concurrency — threads, processes and asyncio module: a graded quiz and whole-program exercises, passed at 70%.
Instructions
This checkpoint covers the whole module: the GIL and the three models, threads with Lock, Queue and Event, the race and its fix, concurrent.futures executors with ordered map and unordered as_completed, multiprocessing with pickling and the main guard, and asyncio from coroutines and gather through TaskGroup, semaphores, queues and bridging to blocking code — plus the determinism rule that every judged program obeys.
How it works. Twelve questions and three programs. You need 70% on the questions and every program accepted to clear the module. You can retake it as often as you like; your best score counts.
Before you start, make sure you can answer these from memory:
- Why do threads not speed up a CPU-bound loop in CPython, and what does speed it up?
- What are the three bytecodes behind
counter += 1, and where can the switch happen? - What is the difference between
executor.mapandas_completedin the order of results? - Why must code that starts processes live under
if __name__ == "__main__":? - What happens when a coroutine is called without
await? - What does
asyncio.gatherreturn, and in what order? - What is the one way to make a threaded program's output deterministic?
The three programs are a threaded word counter whose workers fill per-thread slots and are merged after join, a pipeline of workers reading a Queue with sentinels whose per-worker results are merged in order, and an asyncio job runner with a semaphore that records a deterministic trace through sleep(0) yields and reports results with gather.
Common questions
Why do threads not speed up a CPU-bound loop in CPython?
The GIL lets only one thread execute Python bytecode at a time, so CPU-bound threads take turns rather than run in parallel. Processes, through ProcessPoolExecutor or multiprocessing, or a C extension that releases the GIL, do speed it up.
What does asyncio.gather return, and in what order?
A list of the awaitables' results in the order the arguments were given, regardless of which finished first. An exception propagates to the caller unless return_exceptions=True collects it as a value in the list.
What is the one way to make a threaded program's output deterministic?
Give every worker its own result slot, join all threads before reading, and print the aggregated, ordered results from the main thread afterwards. A worker that prints directly interleaves with the others in an arbitrary order.
Exercises
Threaded word counter
Read T on the first line, then text lines until EOF. Thread i handles lines[i::T]: it lowercases each line, splits on whitespace, strips .,;:!? from each word, and counts the words into its own collections.Counter in slots[i]. Join the threads, merge the counters, and print total <number of words> followed by the top three words as <word> <count> ordered by count descending then word ascending (fewer if there are fewer distinct words).
Input: T, then lines of text. Output: the total, then up to three lines.
2
the cat sat on the mat
the dog sat
a cat
prints
total 11
the 3
cat 2
sat 2Queue pipeline by index
Read W on the first line, then text lines until EOF. Put (index, line) jobs on a queue.Queue followed by W sentinels. Each of W worker threads takes jobs and, for each, computes "<index>: <words> words, <chars> chars, longest <word>" where words is the whitespace word count, chars is len(line) and longest is the first longest word; it appends (index, text) to a shared results list under a Lock. Join the workers, sort the results by index, and print each text.
Input: W, then lines. Output: one line per input line, in input order.
2
the quick brown fox
jumps over
the lazy dog
prints
0: 4 words, 19 chars, longest quick
1: 2 words, 10 chars, longest jumps
2: 3 words, 12 chars, longest lazyAsync job runner with a trace
Read limit on the first line, then lines <name> <steps> until EOF. Write async def run(name, steps) that, inside async with sem: for a Semaphore(limit), appends start <name> to a shared trace list, awaits asyncio.sleep(0) steps times, appends done <name>, and returns (name, sum(range(1, steps + 1))). Gather every job in input order; print each trace line, then <name> -> <value> for each result in gather order.
Input: limit, then one job per line. Output: the trace, then the results.
2
a 2
b 1
c 1
prints
start a
start b
done b
done a
start c
done c
a -> 3
b -> 1
c -> 1In this module: Concurrency — threads, processes and asyncio
- The GIL and the three models — threads, processes, asyncio
- Threads — Thread, Lock, Event, Queue and the race you must see once
- concurrent.futures — executors, futures, map and as_completed
- multiprocessing — Process, Pool, pickling, queues and shared state
- asyncio basics — coroutines, await, tasks and gather
- asyncio patterns — TaskGroup, queues, semaphores, async iteration and bridging
- Checkpoint — Concurrency (this lesson)
← asyncio patterns — TaskGroup, queues, semaphores, async iteration and bridging · The testing mindset — what to test, how to arrange it, and designing for testability →