Creating and Starting Threads
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
Section titled “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
Section titled “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
Section titled “Common mistakes”1) Forgetting join()
Section titled “1) Forgetting join()”If you don’t join(), the main program may exit early in some cases.
2) Sharing data without protection
Section titled “2) Sharing data without protection”Threads share memory. You need locks (next pages).
Check yourself
Section titled “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.
pch.quizShowAnswer
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.
pch.quizShowAnswer
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`.
pch.quizShowAnswer
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.
pch.quizShowAnswer
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
Section titled “🧪 Try It Yourself”Exercise 1 – Create a Simple Thread
Section titled “Exercise 1 – Create a Simple Thread”Exercise 2 – Join a Thread
Section titled “Exercise 2 – Join a Thread”Exercise 3 – Run Multiple Threads
Section titled “Exercise 3 – Run Multiple Threads”pch.coffeeTagline
pch.coffeeCtapch.feedbackHeading
pch.feedbackSubheading