liveBook · Manning

Global interpreter lock

As explained in Fast Python: High performance techniques for large datasets

The global interpreter lock is a mutual-exclusion mechanism in CPython that permits only one thread at a time to execute Python bytecode within a process.

The global interpreter lock (GIL) limits CPython to one thread executing Python code at a time. OS threads can interleave, but CPU-bound Python threads do not execute in parallel across cores. Concurrency means tasks overlap in time; parallelism means they run at the same time. The distinction matters when choosing how to speed up data processing. section 1.3.1 section 3.3.3

Futures are not proof of parallelism

Submitting mapper calls to a ThreadPoolExecutor returns futures that can be checked while work is pending. This makes a MapReduce pipeline concurrent, but the GIL prevents its Python mapper threads from running in parallel. A mapper that sleeps illustrates why overlap can still be useful when tasks spend time waiting. section 3.3.2

from collections import defaultdict
from concurrent.futures import ThreadPoolExecutor as Executor
 
def async_map(executor, mapper, data):
    futures = []
    for datum in data:
        futures.append(executor.submit(mapper, datum))   #1
    return futures
 
def map_less_naive(executor, my_input, mapper):
    map_results = async_map(executor, mapper, my_input)
    return map_results

The thread-pool mapper submits each datum and returns futures rather than completed results. That change enables asynchronous progress; it does not, by itself, remove the GIL’s limit on Python execution. section 3.3.2

The GIL is not necessarily the dominant performance cost. Multicore thread switching can add friction, while high-performance inner loops often belong in lower-level code. Threading can still serve I/O-heavy work or program structure; it is rarely the best route to faster CPU-bound Python code. section 1.3.1 section 3.3.3 section 3.6

Choose an escape route according to where the expensive work runs:

  • Use multiprocessing for parallel pure-Python computation. Keep tasks coarse enough that communication overhead does not erase the gain. section 3.4 section 3.6
  • Move the critical loop into lower-level code that can release the GIL. NumPy, SciPy, and scikit-learn can run parallel work in their C or Fortran implementations. section 3.3.3
  • For custom array work, reduce CPython interactions in Cython before attempting GIL-independent threaded execution. A color Game of Life implementation provides a concrete array-processing target. section 5.6 section 5.8

How Fast Python: High performance techniques for large datasets covers it

How deep it goes
Defines the execution limit and compares practical escape routes, without detailing how the lock is acquired or scheduled.
Examples
Uses a futures-based MapReduce pipeline and a Cython color Game of Life project.
What it assumes
Assumes familiarity with Python threads and data-processing code, while defining concurrency and parallelism explicitly.
What you can do after
Evaluate/understand when threads provide useful overlap and when multiprocessing or GIL-releasing native code is needed for parallel computation.
How it connects
Connects the GIL to concurrency, parallelism, futures, multiprocessing, NumPy, and Cython.

Also explained in

Compare how 5 books explain global interpreter lock →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Fast Python: High performance techniques for large datasets. The section links point to the parts of the book it draws on.