CPython's Global Interpreter Lock (GIL) allows only one thread to execute Python bytecode at a time, so `threading` helps for I/O-bound work (waiting on network/disk) but not CPU-bound work.
python
import threading
def fetch(url, results, i):
results[i] = f"fetched {url}"
results = [None, None]
threads = [threading.Thread(target=fetch, args=(f"url{i}", results, i)) for i in range(2)]
for t in threads: t.start()
for t in threads: t.join()For CPU-bound work (heavy computation like hashing millions of items), `multiprocessing` spawns separate processes, each with its own interpreter and GIL, achieving real parallelism at the cost of inter-process communication overhead.