Create a thread, start it, and wait for it. The four verbs you'll use every single time.
import time
def job(name):
time.sleep(1) # simulate I/O wait
print(f"{name} done")
job("A"); job("B"); job("C") # 3 seconds total โ each waits for the last
Every thread you ever use follows the same lifecycle:
t = threading.Thread(target=fn, args=(...)) โ nothing runs yet.t.start() โ schedules fn to run in a new OS thread. Returns immediately.fn concurrently with your other threads.t.join() โ blocks the caller until t has finished.start() with run(): t.run()
executes the function in the current thread (no concurrency!). Always call
start().
start() them all, then join()
them all. Starting inside the first loop and joining in a second loop is what makes them overlap.
import threading, time
def job(name):
print(f"{name} start")
time.sleep(1) # I/O wait โ GIL released here
print(f"{name} done")
threads = [threading.Thread(target=job, args=(n,)) for n in "ABC"]
for t in threads:
t.start() # kick them all off first
for t in threads:
t.join() # THEN wait for all โ total โ 1s
print("all finished")
t.start(); t.join() โ runs them sequentially again, because you wait for each one before
starting the next. Split the loops.
Thread gives you no built-in way to read what target returned. You either
write into a shared container, or (much better) use a ThreadPoolExecutor,
which hands back a Future.
results = {}
def fetch(name):
time.sleep(1)
results[name] = f"data-for-{name}" # write into shared dict
threads = [threading.Thread(target=fetch, args=(n,)) for n in "ABC"]
for t in threads: t.start()
for t in threads: t.join()
print(results) # {'A': 'data-for-A', 'B': ..., 'C': ...}
You'll see this in older code: subclass Thread and override run(). Prefer
target= or the executor for new code โ but you should recognize it.
class Worker(threading.Thread):
def __init__(self, name):
super().__init__()
self.name = name
self.result = None
def run(self): # override run(), but still call .start()
time.sleep(1)
self.result = f"done-{self.name}"
w = Worker("A")
w.start()
w.join()
print(w.result) # done-A
| Verb | What it does | Blocks? |
|---|---|---|
Thread(target=..., args=...) | Builds the thread object | No โ nothing runs |
.start() | Runs target in a new thread | No โ returns instantly |
.join() | Wait for the thread to finish | Yes โ blocks the caller |
.is_alive() | Check if still running | No |