Fundamentals 15 min read

Python Concurrency Programming: Threads, Processes, GIL, and Thread Pools

This article explains how Python concurrency can boost program performance, covering multithreading, multiprocessing, the Global Interpreter Lock, thread safety, thread pools, and practical code examples for speeding up I/O‑bound and CPU‑bound tasks.

TAL Education Technology
TAL Education Technology
TAL Education Technology
Python Concurrency Programming: Threads, Processes, GIL, and Thread Pools

Introduction

Python introduces concurrent programming to improve execution speed. Mastering concurrency—especially multithreading and multiprocessing—is essential for senior developers because they address I/O‑bound and CPU‑bound problems respectively.

Why introduce concurrency?

Examples show that a sequential web crawler takes 1 hour, while concurrent downloading reduces it to 20 minutes; a personalized homework recommendation system processes 3,000 users in 17 s with a single thread but only 0.5 s with multiprocessing. The conclusions are:

Concurrency speeds up programs by better CPU utilization.

It enables task decomposition and parallel execution.

It is a required skill for advanced programmers.

Methods to accelerate Python programs

Python supports several long‑term concurrency techniques: multithreading, multiprocessing, and generator‑based coroutines (the latter not covered here). An illustration of these methods is provided.

Choosing between multithreading and multiprocessing

CPU‑bound tasks benefit from multiprocessing, while I/O‑bound tasks benefit from multithreading. The article explains the characteristics of each:

CPU‑bound (high CPU usage): examples include compression, encryption, regex search.

I/O‑bound (low CPU usage): examples include file handling, web crawling, database access.

Python’s concurrency primitives

Key modules and classes:

threading – lightweight threads sharing memory.

multiprocessing – separate processes that can run on multiple CPU cores.

asyncio – asynchronous I/O in a single thread.

Synchronization tools: Lock , Queue , thread pools, process pools.

Global Interpreter Lock (GIL)

The GIL ensures that only one Python thread executes bytecode at a time, which limits multithreaded CPU‑bound performance. While Python can create many threads, they contend for the GIL, so only one runs on the CPU at any moment.

Reasons for the GIL include simplifying memory management and reference counting. Removing the GIL is historically difficult.

Mitigating GIL limitations

For I/O‑bound workloads, multithreading remains effective because threads spend most of their time waiting. For CPU‑bound workloads, use multiprocessing to achieve true parallelism across cores.

Multithreading practice

Example code shows how to create and start a thread:

# 1. Prepare a function
def my_func(a, b):
    craw(a, b)

# 2. Create a thread
t = threading.Thread(target=my_func, args=(100, 200))

# 3. Start the thread
t.start()

# 4. Wait for completion
t.join()

A complete crawler example compares single‑threaded and multithreaded execution, demonstrating a speedup from ~3.8 s to 0.37 s.

Thread safety

When multiple threads modify shared variables, race conditions occur. The article shows a counter example where three threads increment a global variable 100 000 times each, producing an incorrect final value due to lack of synchronization.

Locks can resolve this:

# try‑finally style
import threading
lock = threading.Lock()
lock.acquire()
try:
    # critical section
    pass
finally:
    lock.release()

# with‑statement style
import threading
lock = threading.Lock()
with lock:
    # critical section
    pass

Thread pools

Thread pools reuse worker threads to avoid the overhead of creating and destroying threads. The pool workflow includes checking for idle threads, creating new ones up to corePoolSize , queuing tasks, and expanding up to maximumPoolSize when needed.

Benefits:

Performance gain by reusing threads.

Suitable for bursty workloads with many short‑lived tasks.

Prevents system overload from excessive thread creation.

Simpler syntax compared to manual thread management.

Example using ThreadPoolExecutor :

from concurrent.futures import ThreadPoolExecutor, as_completed

# map style (order preserved)
with ThreadPoolExecutor() as pool:
    results = pool.map(craw, urls)
    for result in results:
        print(result)

# future style (order not guaranteed)
with ThreadPoolExecutor() as pool:
    futures = [pool.submit(craw, url) for url in urls]
    for future in as_completed(futures):
        print(future.result())

Multiprocessing

Multiprocessing overcomes the GIL by running separate processes on multiple CPUs. It is advantageous for CPU‑bound tasks, whereas multithreading remains useful for I/O‑bound workloads.

Performance comparison in a personalized homework recommendation scenario shows that multiprocessing only outperforms single‑threaded execution when the sample size exceeds about 70 items.

References

1. https://blog.csdn.net/liuxiao723846/article/details/108026782 2. http://c.biancheng.net/view/2606.html 3. https://blog.csdn.net/gua_niu123/article/details/111350343 4. https://blog.csdn.net/qq_50840738/article/details/123861602 5. https://blog.51cto.com/u_14575624/4369560 6. 蚂蚁学Python – Python concurrency 7. 《Python+Cookbook》第三版中文v3.0.0

PerformancepythonConcurrencythreadpoolmultithreadingGILmultiprocessing
TAL Education Technology
Written by

TAL Education Technology

TAL Education is a technology-driven education company committed to the mission of 'making education better through love and technology'. The TAL technology team has always been dedicated to educational technology research and innovation. This is the external platform of the TAL technology team, sharing weekly curated technical articles and recruitment information.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.