`threading` — OS-Level Threads
Python · Concurrency · Run with python3
Explanation
`threading.Thread` runs a callable in an OS thread. Threads share the process's memory — communication is via shared objects, protected by `threading.Lock`, `RLock`, `Semaphore`, `Event`, `Condition`, and `Barrier`. `threading.local()` provides thread-local storage. Threads are well-suited for I/O-bound concurrency: database queries, HTTP requests, file I/O. A `Daemon` thread dies automatically when the main thread exits.
Real-World Scenario
A web scraper fetches 100 URLs concurrently using 20 threads. Each thread blocks on network I/O — the GIL is released during `socket.recv`, so threads truly run concurrently at the network level. Total time ≈ max single-URL latency, not sum. ```python import threading import time import random from queue import Queue results: Queue[tuple[str, int]] = Queue() def fetch(url: str) -> None: """Simulates an HTTP request with random latency.""" time.sleep(random.uniform(0.05, 0.3)) status = 200 if random.random() > 0.1 else 500 results.put((url, status)) urls = [f"https://api.example.com/item/{i}" for i in range(10)] # Thread pool via list start = time.perf_counter() threads = [threading.Thread(target=fetch, args=(u,)) for u in urls] for t in threads: t.start() for t in threads: t.join() elapsed = time.perf_counter() - start print(f"Fetched {results.qsize()} URLs in {elapsed:.2f}s") successes = sum(1 for _ in range(results.qsize()) if results.get()[1] == 200) ```
Quick Snippet
import threading
import time
import random
from queue import Queue
results: Queue[tuple[str, int]] = Queue()
def fetch(url: str) -> None:
"""Simulates an HTTP request with random latency."""
time.sleep(random.uniform(0.05, 0.3))
status = 200 if random.random() > 0.1 else 500
results.put((url, status))
urls = [f"https://api.example.com/item/{i}" for i in range(10)]
# Thread pool via list
start = time.perf_counter()
threads = [threading.Thread(target=fetch, args=(u,)) for u in urls]
for t in threads: t.start()
for t in threads: t.join()
elapsed = time.perf_counter() - start
print(f"Fetched {results.qsize()} URLs in {elapsed:.2f}s")
successes = sum(1 for _ in range(results.qsize()) if results.get()[1] == 200)Full Example
# Compile: python3 threading_basics.py
# Output:
# Fetched 10 URLs
import threading
import time
import random
from queue import Queue
results: Queue = Queue()
def fetch(url: str) -> None:
time.sleep(random.uniform(0.01, 0.05))
status = 200 if random.random() > 0.1 else 500
results.put((url, status))
urls = [f"https://api.example.com/item/{i}" for i in range(10)]
start = time.perf_counter()
threads = [threading.Thread(target=fetch, args=(u,)) for u in urls]
for t in threads: t.start()
for t in threads: t.join()
elapsed = time.perf_counter() - start
fetched = []
while not results.empty():
fetched.append(results.get())
print(f"Fetched {len(fetched)} URLs in {elapsed:.3f}s")
successes = sum(1 for _, s in fetched if s == 200)
print(f"Success: {successes}/{len(fetched)}")
# Thread-local storage
local = threading.local()
def worker(name: str) -> None:
local.name = name
time.sleep(0.01)
print(f"Thread sees: {local.name}")
threads = [threading.Thread(target=worker, args=(f"worker-{i}",)) for i in range(3)]
for t in threads: t.start()
for t in threads: t.join()
python3 threading_basics.py