Creating Threads
Use threading.Thread to create and start threads.
import threading
import time
def download(site):
print(f"Downloading {site}...")
time.sleep(2) # Simulate network I/O
print(f"Done: {site}")
# Without threads: takes 6 seconds
# With threads: takes ~2 seconds
threads = []
for site in ["google.com", "github.com", "python.org"]:
t = threading.Thread(target=download, args=(site,))
threads.append(t)
t.start()
for t in threads:
t.join() # Wait for all to complete
print("All downloads done")The GIL β Global Interpreter Lock
The GIL prevents multiple threads from executing Python bytecode simultaneously. This means threads DO NOT speed up CPU-bound tasks (computation). They DO help I/O-bound tasks because threads release the GIL while waiting for I/O.
Use threading for I/O-bound tasks (HTTP, files, databases). Use multiprocessing for CPU-bound tasks (calculations, image processing). Using threads for heavy computation will NOT improve performance due to the GIL.
Thread Safety β Using Locks
When threads share data, use a Lock to prevent race conditions.
import threading
counter = 0
lock = threading.Lock()
def increment():
global counter
for _ in range(100_000):
with lock: # Only one thread at a time
counter += 1
threads = [threading.Thread(target=increment) for _ in range(5)]
for t in threads: t.start()
for t in threads: t.join()
print(f"Counter: {counter}") # Always 500000 with lockThreadPoolExecutor β High-Level API
concurrent.futures.ThreadPoolExecutor is the modern, higher-level threading API.
from concurrent.futures import ThreadPoolExecutor
import time
def fetch(url):
time.sleep(1) # Simulate I/O
return f"Data from {url}"
urls = ["api.com/1", "api.com/2", "api.com/3"]
with ThreadPoolExecutor(max_workers=3) as executor:
results = list(executor.map(fetch, urls))
for r in results:
print(r)