Skip to content
UniCircle
HardConceptualConcurrency

The GIL: when do threads help in Python, and when do you need multiprocessing or asyncio?

Question PY-GIL-007

What is the Global Interpreter Lock (GIL) in CPython? Given (a) downloading 200 web pages and (b) resizing 200 images in pure Python, which of threading, multiprocessing and asyncio would you use for each, and why?

Show the explanation

The GIL is a lock inside the standard CPython interpreter. It lets only one thread run Python bytecode at a time within a process. It keeps the interpreter's internals, such as reference counts, simple and safe. The cost is that pure-Python, CPU-heavy code does not speed up with more threads.

The GIL is released while a thread waits, for example on network or disk I/O or time.sleep. Many C extensions (NumPy, hashing, compression) also release it during heavy work. So:

WorkloadGood choiceWhy
I/O-bound (a: downloading pages, DB calls)threading / concurrent.futures.ThreadPoolExecutor, or `asyncio`Threads spend most of their time waiting, with the GIL released. asyncio runs thousands of waits on one thread with very little overhead, but needs async-aware libraries.
CPU-bound pure Python (b: resizing images in a Python loop)multiprocessing / ProcessPoolExecutorEach process has its own interpreter and its own GIL, so all cores are used. The cost: starting processes and pickling data between them.
CPU-bound inside NumPy or other native librariesoften a plain loop or threads are fineThe heavy work runs in C, outside the GIL.

asyncio vs threads: asyncio uses cooperative multitasking. Tasks give up control only at await, so there are fewer race conditions, but one blocking call (such as time.sleep or requests.get) stalls everything. Threads are switched preemptively by the OS, so shared data still needs locks. The GIL does not make your own code thread-safe. counter += 1 is several bytecode steps and can still lose updates.

Recent changes, as of this writing. CPython 3.13 added an optional, experimental free-threaded build with no GIL (PEP 703). Python 3.14 moved it to officially supported status, but it is still a separate build and not the default. Other implementations differ too: Jython has no GIL, and PyPy has its own. In an interview, say that CPython's default build has a GIL, then describe the free-threaded option.

Summary answer: (a) a thread pool or asyncio; (b) a process pool, or move the work to a native library (Pillow, OpenCV) that does it in C.

Original UniCircle question. Last reviewed 22 Oct 2026.