threading vs multiprocessing
threading vs multiprocessing
Section titled βthreading vs multiprocessingβWhat it is
Section titled βWhat it isβBoth are standard-library modules for running code concurrently, but with fundamentally different execution models: threading runs multiple threads inside one process, sharing memory directly but limited by the GIL to one thread executing Python bytecode at a time. multiprocessing runs multiple separate OS processes, each with its own interpreter and memory space β no GIL contention, but no free memory sharing either.
Before this feature
Section titled βBefore this featureβWithout either, concurrent-looking work has to be simulated with manual interleaving or just run sequentially β no real overlap during I/O waits, no real parallelism for CPU work:
def download(url): ... # blocks for however long the network call takes
for url in urls: download(url) # entirely sequential β each waits for the previous to finishAfter this feature
Section titled βAfter this featureβ# threading β great for I/O-bound work (waiting on network/disk)import threading
def download(url, results, i): results[i] = fetch(url) # blocks on I/O, but releases the GIL while waiting
results = [None] * len(urls)threads = [threading.Thread(target=download, args=(u, results, i)) for i, u in enumerate(urls)]for t in threads: t.start()for t in threads: t.join()# all downloads overlap while waiting on the network β real speedup# multiprocessing β needed for CPU-bound work (actual computation)from multiprocessing import Pool
def crunch_numbers(n): return sum(i * i for i in range(n))
if __name__ == "__main__": with Pool(processes=4) as pool: results = pool.map(crunch_numbers, [10_000_000] * 4) # genuinely runs on 4 cores in parallel β each process has its own GILWhy this is better
Section titled βWhy this is betterβ- Match the tool to the bottleneck:
threadingfor I/O-bound (network calls, file I/O, database queries β mostly waiting);multiprocessingfor CPU-bound (number crunching, image processing, data transformation β mostly computing). threadingis lighter weight: threads share memory directly and are cheaper to create than processes, so itβs the better default when the GIL isnβt actually the bottleneck (i.e., I/O-bound work).multiprocessinggets real parallelism: each process has its own GIL, so CPU-bound work genuinely spreads across cores β somethingthreadingstructurally cannot do in CPython.
Key notes / edge cases
Section titled βKey notes / edge casesβ- Sharing data between threads is direct (same memory) but requires locks (
threading.Lock) to avoid race conditions on shared mutable state; sharing data between processes requires explicit serialization (multiprocessing.Queue,Pipe, or shared-memory objects) since they donβt share an address space. multiprocessinghas real overhead: process startup is slower than thread startup, and passing data between processes means pickling/unpickling it β for small, fast tasks that overhead can outweigh the parallelism gained.- Both are increasingly complemented (not replaced) by
asynciofor I/O-bound work with very high concurrency (thousands of simultaneous connections) β see async/await β since one thread running an event loop can juggle far more concurrent I/O than one thread per connection would allow. - On some platforms,
multiprocessingβs default start method requires theif __name__ == "__main__":guard shown above, or child processes can end up re-importing and re-running the parent script.
Quick practice
Section titled βQuick practiceβ-
Would you reach for
threadingormultiprocessingto speed up downloading 100 files over the network?Answer
`threading` β it's I/O-bound (mostly waiting), and threads release the GIL during I/O waits, so they genuinely overlap. -
Would you reach for
threadingormultiprocessingto speed up resizing 100 large images?Answer
`multiprocessing` β that's CPU-bound work; threads can't run Python bytecode in parallel due to the GIL, so only separate processes get real parallelism. -
Why is sharing data between processes harder than between threads?
Answer
Threads share the same memory space directly; processes don't, so data has to be explicitly serialized and sent through a `Queue`/`Pipe` or a shared-memory construct.