flowchart TD
A["t = Thread(target=work)"] --> B{"how do you launch it?"}
B -->|"t.start()"| C["the OS creates a thread"]
C --> D["run() is invoked THERE"]
D --> E["work() executes on Thread-1"]
B -->|"t.run()"| F["an ordinary method call"]
F --> G["work() executes on the CALLING thread"]
G --> H["fully sequential -- and it looks like it worked"]
E --> I["t.join() waits for it"]
I --> J["t.is_alive() is False afterwards"]
J --> K["t.start() again -> RuntimeError: threads can only be started once"]
Basic Thread usage
import threading
def greet(name: str) -> None:
print("Hello", name)
t = threading.Thread(target=greet, args=("Ravi",))
t.start()
t.join()
print("Main thread finished")Naming threads
Naming is helpful for logs.
import threading
def work():
print("Running in:", threading.current_thread().name)
t = threading.Thread(target=work, name="worker-1")
t.start()
t.join()Common mistakes
1) Forgetting join()
If you don’t join(), the main program may exit early in some cases.
2) Sharing data without protection
Threads share memory. You need locks (next pages).
Check yourself
You call `t.run()` instead of `t.start()`. What happens?
Verified by asking the target which thread it was on: `start()` reported `Thread-1`, `run()` reported `MainThread`. Nothing raises, so the program looks correct and is simply sequential.
Show answer
B — The target runs on the calling thread — no concurrency, no error — Verified by asking the target which thread it was on: `start()` reported `Thread-1`, `run()` reported `MainThread`. Nothing raises, so the program looks correct and is simply sequential.
What does calling `start()` on a thread that has already finished do?
Verified. A Thread object is single-use — create a new one per run.
Show answer
C — Raises RuntimeError: threads can only be started once — Verified. A Thread object is single-use — create a new one per run.
How do you reliably know a worker thread has finished?
`join()` waits; `is_alive()` confirms. There is no `stop()` — a thread must be asked to finish, usually by checking a `threading.Event`.
Show answer
B — Call `t.join()`, then `t.is_alive()` is False — `join()` waits; `is_alive()` confirms. There is no `stop()` — a thread must be asked to finish, usually by checking a `threading.Event`.
Why does threading often fail to speed up CPU-bound Python?
Measured elsewhere in this course: CPU-bound work on a 4-thread pool came out at 0.92x — slower than sequential. I/O-bound work on the same pool reached 9.2x, because a waiting thread releases the GIL.
Show answer
B — The GIL lets only one thread run Python bytecode at a time — Measured elsewhere in this course: CPU-bound work on a 4-thread pool came out at 0.92x — slower than sequential. I/O-bound work on the same pool reached 9.2x, because a waiting thread releases the GIL.
🧪 Try It Yourself
Exercise 1 – Create a Simple Thread
Tab indents. Press Escape, then Tab, to leave the editor. Control plus Enter runs the code; Control plus Shift plus Enter submits it.
Press Run to see what your code prints.Exercise 2 – Join a Thread
Tab indents. Press Escape, then Tab, to leave the editor. Control plus Enter runs the code; Control plus Shift plus Enter submits it.
Press Run to see what your code prints.Exercise 3 – Run Multiple Threads
Tab indents. Press Escape, then Tab, to leave the editor. Control plus Enter runs the code; Control plus Shift plus Enter submits it.
Press Run to see what your code prints.