Concurrency line
Threading deep-dive
A cook can be hired on paper before he walks in, and you wait at the pass until his shift ends.
Stops explain each step. The film plays the whole ride.
- Walking in or cooking
- Waiting
- Coming back or finishing
- Cut off
The contract is only paper
The cook is hired on paper. Nothing is running, and he is not in the kitchen yet.
Next stop: He walks into the kitchen
All stops
The everyday version
One cook. The contract is the hire. Walking in starts the shift at his own counter. The door is a delivery. The pass is you, waiting until he is done.
- The contractis the Thread object
The hire on paper. He is not cooking yet.
- His counteris the OS thread
His own bench. Calls and local notes stay here, not on a shared pile.
- The stoveis the Interpreter
Where the recipe runs. On a normal setup, one cook at a time.
- The dooris the Blocked call
A delivery. He is not cooking, so someone else may take the stove.
- The passis the join
You, waiting until the shift ends.
- A second cookis the next page
Two cooks and one tally. That fight is not this page.
Interview questions
Say the short answer first. Go deeper only if they ask.
What is the difference between the Thread object and the OS thread?
The object can exist before the cook walks in. The real thread begins then.
ident is None until start. is_alive tracks run, from just before it until just after it returns, not the lifetime of the object. Creating a bare Thread does not start it. Submitting to a ThreadPoolExecutor does start work.
“Creating the Thread starts it.”
What does daemon mean?
The process will not stay up just for that thread.
The process exits when no non-daemon thread is alive, and daemon threads are stopped abruptly. Set the flag before start. A clean stop is a non-daemon thread plus an Event. Joining a daemon during late finalization can raise PythonFinalizationError in 3.14.
“Daemon means low priority.”
Why does each thread have a stack?
So its calls and local notes are not mixed with another cook’s.
Threads share the heap. The stack is private. stack_size(0) keeps the platform default for threads created afterward, and the smallest size you may set is 32 KiB. The docs do not state an 8 MB default.
“Threads share one stack.”
What does join actually tell you?
It waits until the shift ends, and it returns nothing.
join returns None. A timeout does not mean the thread finished: call is_alive afterward. Joining yourself, or joining before start, raises RuntimeError. An exception inside run is handed to threading.excepthook, which ignores SystemExit.
“join returns the thread’s result.”
What is a thread that Python did not start?
It shows up as a dummy. You cannot wait for it at the pass.
A thread started outside the threading module becomes a dummy when Python notices it. Dummies are always alive, always daemonic, and cannot be joined. Mention them once. Do not build a design around joining one.
“Every thread you can see can be joined.”
Cheat-sheet
Commands you will actually type.
t = threading.Thread(target=work)
print(t.ident, t.is_alive()) # None FalseThe object exists. No kernel thread yet, and it is not alive.
t.daemon = True # before start, or RuntimeError
t.start()The flag is inherited, and the main thread is not a daemon.
t.join(1.0) # returns None
still = t.is_alive()A timeout does not say the thread finished. Ask is_alive after it.
threading.stack_size(0) # platform default
# smallest settable size is 32 KiBZero keeps the platform default. Do not assume 8 MB.
stop = threading.Event()
# non-daemon thread: stop.wait() until stop.set()A clean stop. A daemon is cut off when the process exits.