Threading & The GIL
Threading is a form of concurrency where multiple “threads of execution” run within a single process, sharing the same memory space. In Python, threading is the go-to tool for I/O-bound tasks—operations where the CPU spends most of its time waiting (e.g., waiting for a website to respond or a file to be read).
1. The Global Interpreter Lock (GIL)
Section titled “1. The Global Interpreter Lock (GIL)”Before using threads, you must understand the GIL.
CPython (the standard Python engine) has a mutex that protects access to Python objects, preventing multiple threads from executing Python bytecodes at once.
The Implications:
Section titled “The Implications:”- No Parallelism: Even on a 16-core CPU, Python threads run on only one core at a time.
- I/O Efficiency: While one thread is waiting for a network response, it “releases” the GIL, allowing another thread to run. This makes threading excellent for web scraping or network servers.
- CPU Bottleneck: If you use threads for heavy math (CPU-bound), they will actually run slower than a single thread because they will fight over the GIL.
2. Basic Threading
Section titled “2. Basic Threading”We use the built-in threading module to create and manage threads.
import threadingimport time
def download_file(name): print(f"Starting download: {name}") time.sleep(2) # Simulate network delay print(f"Finished: {name}")
# Create threadst1 = threading.Thread(target=download_file, args=("file1.zip",))t2 = threading.Thread(target=download_file, args=("file2.zip",))
t1.start()t2.start()
# .join() tells the main program to wait for these threads to finisht1.join()t2.join()
print("All downloads complete.")3. The Race Condition
Section titled “3. The Race Condition”Because threads share the same memory, they can try to modify the same variable at the exact same time. This is a Race Condition.
import threading
balance = 0
def deposit(): global balance for _ in range(1000000): balance += 1 # This is NOT an atomic operation!
t1 = threading.Thread(target=deposit)t2 = threading.Thread(target=deposit)
t1.start(); t2.start()t1.join(); t2.join()
print(f"Final Balance: {balance}")# Expected: 2,000,000. Actual: Something much smaller!The Solution: Locks
Section titled “The Solution: Locks”A Lock ensures that only one thread can execute a specific block of code at a time.
lock = threading.Lock()
def safe_deposit(): global balance with lock: # The thread 'acquires' the lock balance += 1 # Lock is 'released' automatically after the block4. Under the Hood: Context Switching
Section titled “4. Under the Hood: Context Switching”How does Python run multiple threads on one core? The operating system performs Context Switching. It pauses one thread, saves its state, and loads the state of another thread. This happens thousands of times per second, creating the illusion of simultaneous execution.
5. Summary Table
Section titled “5. Summary Table”| Feature | Behavior |
|---|---|
| Best For | I/O-bound tasks (Network, Disk). |
| Memory | Shared between all threads. |
| Speed | Fast to start, but limited by the GIL. |
| Danger | High risk of Race Conditions. |
| Tool | threading.Thread and threading.Lock. |