Threads: start, join, and why they help with waiting
A thread runs a function alongside the rest of the program. While one thread waits for a server, others can work, so forty slow requests take about as long as the slowest one. The pattern is always: start them all, then join them all.
Read and diagnose: this can't run in the browser, so you'll read real code and output and answer questions about it.
- Create threads with
threading.Thread(target=..., args=...)and explain start() and join() - Write the start-all-then-join-all pattern, and collect results from inside the threads
- Explain when threads speed a program up (waiting) and when they don't (pure computation)
A thread is a separate line of execution inside the same program, sharing its memory. threading.Thread(target=func, args=(...,)) describes one; start() begins running func in it while your code carries on; join() waits until it has finished. Threads pay off when the work mostly waits: for a network reply, a disk, a database. A waiting thread releases the GIL (“The interpreter: bytecode and the GIL”), so the waits overlap.
Python in this browser page runs on a single thread, so creating Thread objects works but start() raises an error. The examples show both, and the exercises use the same functions with a stand-in thread class.
Run each example to see what it prints. Running examples doesn't count toward progress.
import threading def send_receipt(order_id): print("receipt for", order_id) worker = threading.Thread(target=send_receipt, args=("O100282",), name="receipts-1") print(worker.name, worker.is_alive()) print(threading.current_thread().name, threading.active_count())
import threading def send_receipt(order_id): return "sent " + order_id threading.Thread(target=send_receipt, args=("O100282",)).start()
The pattern for running many jobs is start every thread first, then join every thread. Joining inside the start loop waits for each job before starting the next, which is just a slow serial loop. To make the function testable (and runnable here), take the thread class as a parameter with threading.Thread as the default: the dependency-injection idea from the Advanced OOP & Design module. The example passes StepThread, a stand-in that notes each start() and runs the work when join() is called.
import threading def send_all(customers, send, make_thread=threading.Thread): threads = [] for customer in customers: threads.append(make_thread(target=send, args=(customer,))) for thread in threads: thread.start() for thread in threads: thread.join() class StepThread: """Stands in for threading.Thread here: start() is noted, the work runs at join().""" log = [] def __init__(self, target, args=()): self.target = target self.args = args def start(self): StepThread.log.append("start " + self.args[0]) def join(self): self.target(*self.args) StepThread.log.append("join " + self.args[0]) sent = [] send_all(["C00077", "C01338"], sent.append, make_thread=StepThread) print(sent) print(StepThread.log)
Two more rules. A thread's return value is thrown away, so a thread stores its result somewhere the main thread can read after join(), such as a dictionary (results[url] = ...; storing one key is safe under CPython's locks). And an exception in a thread doesn't reach `join()`: it is printed and lost. Catch errors inside the thread's function and record them.
When to use threads: many independent waits (HTTP requests, file downloads, database calls). When not to: pure-Python computation (the GIL lets one thread compute at a time, “The interpreter: bytecode and the GIL”; use processes, “Processes: parallel work, separate memory”). Mistakes to avoid: joining inside the start loop; returning a value from the target; letting errors vanish; and unbounded numbers of threads (use a pool, next lesson).
threading.Thread(target=f, args=(x,));start()runs it alongside,join()waits for it.- Start all threads, then join all; results go into a shared dictionary, errors are caught inside the thread.
- Threads help waiting work, not pure-Python computation.
- Next: thread pools, which manage the threads and return results for you.