DEV Community

Aman Kumar
Aman Kumar

Posted on

🚀 Advanced Python Internals: GIL, Multithreading

In this article, we’ll cover:

  • What is the Global Interpreter Lock (GIL)
  • Why Python has GIL
  • Threading in Python
  • Multithreading explained
  • How GIL affects performance
  • Why Python still supports multithreading

1️⃣ Global Interpreter Lock (GIL)

The Global Interpreter Lock (GIL) is a mutex that allows only one thread to execute Python bytecode at a time.

Even on multi-core systems, Python threads cannot execute Python bytecode truly in parallel within a single process.

Why Does Python Have GIL?

Python internally uses:

  • Reference counting
  • Automatic memory management

Without GIL:

  • Multiple threads could modify memory simultaneously
  • Race conditions could occur
  • Memory corruption could happen

The GIL keeps CPython memory management thread-safe.

Basic Example of GIL

import threading
 counter = 0 
def increment():
 global counter 
for _ in range(1000000):
 counter += 1 
thread1 = threading.Thread(target=increment) 
thread2 = threading.Thread(target=increment) 
thread1.start()
thread2.start()
thread1.join()
thread2.join()
print(counter)
Enter fullscreen mode Exit fullscreen mode

Although two threads are running, only one thread executes Python bytecode at a time.

2️⃣ Real-World GIL Scenarios

CPU-intensive tasks do NOT benefit much from threading because of GIL.

Examples:

  • Image processing
  • AI computations
  • Mathematical calculations
  • Data compression

CPU-Bound Example

def heavy_computation():

    total = 0

    for i in range(10**7):
        total += i

    return total
Enter fullscreen mode Exit fullscreen mode

Threads here compete for GIL instead of executing truly in parallel.

I/O tasks benefit greatly from threading because threads release the GIL while waiting.

Examples:

  • API calls
  • Database queries
  • File operations
  • Web scraping

I/O-Bound Example

import threading
 import time 
def fetch_data():
 print("Fetching data...")
 time.sleep(2)
 print("Data fetched")
 t1 = threading.Thread(target=fetch_data) 
 t2 = threading.Thread(target=fetch_data) 
 t1.start()
 t2.start()
 t1.join() 
 t2.join()
Enter fullscreen mode Exit fullscreen mode

3️⃣ Threading in Python

Threading allows multiple tasks to run concurrently inside the same process.

Python provides threading via:

import threading

Basic Thread Example

import threading

def task():
    print("Thread is running")

thread = threading.Thread(target=task)

thread.start()
thread.join()
Enter fullscreen mode Exit fullscreen mode

4️⃣ Multithreading in Python

Multithreading means running multiple threads concurrently.

Multithreading Example

import threading
import time

def worker(name):

    for i in range(3):
        print(f"{name} working...")
        time.sleep(1)

t1 = threading.Thread(target=worker, args=("Thread-1",))
t2 = threading.Thread(target=worker, args=("Thread-2",))

t1.start()
t2.start()

t1.join()
t2.join()
Enter fullscreen mode Exit fullscreen mode

✅ Benefits of Multithreading

  • Better responsiveness
  • Efficient waiting-time usage
  • Improved throughput
  • Useful for backend systems

❌ Limitations of Multithreading

  • GIL blocks true parallelism for CPU-heavy tasks
  • Context switching overhead
  • Synchronization complexity

5️⃣ How GIL Affects Multithreading

Only one thread executes Python bytecode at a time.

Even if:

  • 4 CPU cores exist
  • 4 threads are created

Python threads still compete for GIL.

Visualization
Thread-1 → Running
Thread-2 → Waiting
Thread-3 → Waiting
Thread-4 → Waiting

CPU-Bound Example

def compute():
    sum(i * i for i in range(10**7))
Enter fullscreen mode Exit fullscreen mode

Threads provide little performance gain.

I/O-Bound Example

import requests

requests.get("https://example.com")
Enter fullscreen mode Exit fullscreen mode

Threads help significantly because waiting releases the GIL.

6️⃣ Solution for CPU-Bound Problems

Use:

  • multiprocessing
  • NumPy
  • Async systems
  • C extensions

Multiprocessing Example

from multiprocessing import Process

def task():
    print("Running process")

p1 = Process(target=task)
p2 = Process(target=task)

p1.start()
p2.start()

p1.join()
p2.join()
Enter fullscreen mode Exit fullscreen mode

Each process gets:

  • Separate Python interpreter
  • Separate GIL

7️⃣ Why Python Still Supports Multithreading

A common interview question:

If GIL exists, why does Python allow multithreading?

Because most real-world applications are I/O-bound.

Examples:

  • Web servers
  • APIs
  • File systems
  • Database services

Threads improve responsiveness while waiting for external resources.

Top comments (0)