Multithreading
Hacker’s Parallel Missions
Imagine you’re a hacker running multiple missions at once. One mission downloads files, another cracks passwords, and a third monitors logs. If you run them sequentially, you waste time. But if you run them in parallel, you finish faster and use resources more efficiently.
Python offers two main ways to achieve parallel execution:
- Multithreading → Multiple threads share the same memory space, great for I/O bound tasks.
- Multiprocessing → Multiple processes run independently, each with its own memory, perfect for CPU‑bound tasks.
Why Parallel Execution
- Concurrency vs Parallelism:
- Concurrency → Tasks switch rapidly, giving the illusion of parallelism.
- Parallelism → Tasks truly run at the same time on multiple cores.
- Multithreading: Efficient for tasks waiting on external resources (network, disk).
- Multiprocessing: Efficient for heavy computations (math, image processing).
- Hacker’s Advantage: Parallel execution lets you scale missions, reduce time, and maximize hardware.
Multithreading Example
import threading
import time
def download_file(name):
print(f"Starting download {name}")
time.sleep(2)
print(f"Finished download {name}")
threads = []
for i in range(3):
t = threading.Thread(target=download_file, args=(f"File{i}",))
threads.append(t)
t.start()
for t in threads:
t.join()
- Why? Threads run concurrently, handling multiple downloads without blocking each other.
Multiprocessing Example
import multiprocessing
def compute_square(n):
print(f"Square of {n}: {n*n}")
if __name__ == "__main__":
numbers = [1, 2, 3, 4]
processes = []
for num in numbers:
p = multiprocessing.Process(target=compute_square, args=(num,))
processes.append(p)
p.start()
for p in processes:
p.join()
- Why? Processes run in parallel across multiple CPU cores, ideal for heavy computations.
Using concurrent.futures
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
def task(n):
return n * n
# Thread pool
with ThreadPoolExecutor() as executor:
results = executor.map(task, [1, 2, 3, 4])
print(list(results))
# Process pool
with ProcessPoolExecutor() as executor:
results = executor.map(task, [1, 2, 3, 4])
print(list(results))
- Why?
concurrent.futuresprovides a clean way to manage threads and processes.
Real‑World Example
- Multithreading: Scraping multiple websites simultaneously (I/O‑bound).
- Multiprocessing: Crunching scraped data with heavy analysis (CPU‑bound).
- Hybrid Approach: Use threads for network calls and processes for computation.
The Hacker’s Notebook
- Multithreading is best for I/O‑bound tasks (downloads, network calls). Multiprocessing is best for CPU‑bound tasks (math, image processing).
- Concurrency ≠ parallelism - threads share memory, processes run independently.
concurrent.futuressimplifies managing pools of threads and processes.
Hacker’s Mindset: treat parallel execution as running missions in squads. Threads handle waiting tasks, processes handle heavy lifting, and together they maximize efficiency.

Updated on Jan 3, 2026