Skillversitas

Creating and Joining Threads

Creating threads is a one-liner, but the discipline around starting, joining, and cleaning up is where bugs appear. This chapter shows the standard patterns: passing arguments, running several threads, joining them in order, and propagating results or exceptions back to the caller.

Passing Arguments

import threading
import time

def worker(name, duration):
    print(f"{name} started")
    time.sleep(duration)
    print(f"{name} finished")

threads = []
for i in range(3):
    t = threading.Thread(target=worker, args=(f"T{i}", 1), name=f"Worker-{i}")
    t.start()
    threads.append(t)

for t in threads:
    t.join()
print("all threads joined")

Keyword arguments go through the kwargs dict: threading.Thread(target=fn, kwargs={"retries": 3}).

Capturing Results

Threads have no return value. To get results back, hand the thread a container it can fill, or use concurrent.futures (covered later), which packages this pattern for you:

import threading

results = {}

def compute(key, value):
    results[key] = value * 2

ts = [threading.Thread(target=compute, args=(k, v)) for k, v in {"a": 1, "b": 2}.items()]
[t.start() for t in ts]
[t.join() for t in ts]
print(results)  # {'a': 2, 'b': 4}

Exception Handling

An exception in a thread prints a traceback and kills that thread; it does not crash the program or notify the main thread. If a failure must be visible, catch it inside the target and store it on a shared object:

import threading

class Worker(threading.Thread):
    def __init__(self, fn):
        super().__init__()
        self.fn = fn
        self.error = None

    def run(self):
        try:
            self.fn()
        except Exception as exc:
            self.error = exc

w = Worker(lambda: 1 / 0)
w.start()
w.join()
if w.error:
    print("worker failed:", w.error)

Join with a Timeout

join(timeout=5) returns after five seconds even if the thread is still running, so the main thread can check is_alive() and decide whether to proceed, warn, or abandon the work. A plain join() without a timeout blocks forever, which is fine for short jobs and dangerous for network calls that can hang.

Summary

Start threads with start(), wait with join(), pass data in or out through shared containers, and remember that thread exceptions do not propagate - catch them inside the target when the failure matters.