Skip to content

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).


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.

  • 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.

We use the built-in threading module to create and manage threads.

threading_demo.py
import threading
import time
def download_file(name):
print(f"Starting download: {name}")
time.sleep(2) # Simulate network delay
print(f"Finished: {name}")
# Create threads
t1 = 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 finish
t1.join()
t2.join()
print("All downloads complete.")

Because threads share the same memory, they can try to modify the same variable at the exact same time. This is a Race Condition.

race_condition.py
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!

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 block

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.


FeatureBehavior
Best ForI/O-bound tasks (Network, Disk).
MemoryShared between all threads.
SpeedFast to start, but limited by the GIL.
DangerHigh risk of Race Conditions.
Toolthreading.Thread and threading.Lock.