Master Python Concurrency: Threads, Processes, GIL, and Multiprocessing Explained
This article provides a comprehensive guide to Python concurrency, covering the fundamental differences between threads and processes, the impact of the Global Interpreter Lock, and detailed usage of the multiprocessing module with its various components and synchronization primitives.
Thread and Process Differences
In operating systems, a process is the smallest unit of resource allocation, while a thread is the smallest unit of CPU scheduling. Multiple threads can run within a single process to perform different tasks concurrently.
Key Distinctions
Address space and resources: processes have independent address spaces; threads within the same process share memory.
Communication: processes use IPC mechanisms; threads can directly read/write shared data, requiring synchronization.
Scheduling and switching: thread context switches are much faster than process switches.
In modern OSes, threads are considered essential for concurrency.
Multi‑Process vs Multi‑Thread Comparison
Key points include data sharing, memory usage, creation overhead, programming complexity, reliability, and suitability for distributed systems.
Python Global Interpreter Lock (GIL)
The mechanism used by the CPython interpreter to assure that only one thread executes Python bytecode at a time. This simplifies the CPython implementation by making the object model implicitly safe against concurrent access.
The GIL ensures that only one thread runs Python bytecode simultaneously, which benefits single‑threaded performance and C extension integration but limits true parallelism on multi‑core CPUs.
Execution steps under the GIL:
Acquire GIL
Switch to a thread
Run until a bytecode limit or the thread yields
Set thread to sleep
Release GIL
Repeat
Python 3.2 introduced a timeout‑based GIL release, improving performance on single‑core systems but still causing thrashing on multi‑core CPUs.
Mitigating GIL Impact
Upgrade to newer Python versions with GIL improvements
Use multiprocessing instead of multithreading
Set CPU affinity for threads
Use GIL‑free interpreters like Jython or IronPython
Prefer I/O‑bound workloads for threading
Employ coroutines for efficient single‑threaded concurrency
Write performance‑critical parts in C/C++ extensions (with nogil)
Python Multiprocessing Package
The multiprocessing module provides a process‑based parallelism API that mirrors the threading interface, allowing easy migration from threads to processes.
Background
Multiprocessing was created to overcome GIL limitations and to provide a cross‑platform API for process creation, especially on Windows where fork() is unavailable.
import os
print('Process (%s) start...' % os.getpid())
pid = os.fork()
if pid == 0:
print('I am child process (%s) and my parent is %s.' % (os.getpid(), os.getppid()))
else:
print('Parent process %s created child %s.' % (os.getpid(), pid))Core Components
Process : Create and manage a process.
Pool : Manage a pool of worker processes.
Queue , JoinableQueue : Inter‑process communication queues.
Pipe : Two‑ended communication channel.
Value , Array : Shared memory primitives.
Manager : Server process providing shared objects like list, dict, etc.
Process Example
from multiprocessing import Process
import os
def run_proc(name):
print('Run child process %s (%s)...' % (name, os.getpid()))
if __name__ == '__main__':
print('Parent process %s.' % os.getpid())
p = Process(target=run_proc, args=('test',))
p.start()
p.join()
print('Child process end.')Pool Example
from multiprocessing import Pool
def test(i):
print(i)
if __name__ == '__main__':
pool = Pool(8)
pool.map(test, range(100))
pool.close()
pool.join()Queue Example
from multiprocessing import Process, Queue
import os, time, random
def write(q):
print('Process to write: %s' % os.getpid())
for v in ['A', 'B', 'C']:
print('Put %s to queue...' % v)
q.put(v)
time.sleep(random.random())
def read(q):
print('Process to read: %s' % os.getpid())
while True:
v = q.get(True)
print('Get %s from queue.' % v)
if __name__ == '__main__':
q = Queue()
pw = Process(target=write, args=(q,))
pr = Process(target=read, args=(q,))
pw.start()
pr.start()
pw.join()
pr.terminate()Synchronization Primitives
Lock : Mutual exclusion.
RLock : Re‑entrant lock.
Semaphore : Counting semaphore for limited concurrent access.
Condition : Advanced lock with wait/notify.
Event : Simple flag for signaling.
Lock Example
from multiprocessing import Process, Lock
def l(lock, num):
lock.acquire()
print('Hello Num: %s' % num)
lock.release()
if __name__ == '__main__':
lock = Lock()
for num in range(20):
Process(target=l, args=(lock, num)).start()Concurrent.futures
Python 3.2+ provides ThreadPoolExecutor and ProcessPoolExecutor for high‑level asynchronous execution.
from concurrent import futures
import time
def test(num):
return time.ctime(), num
with futures.ThreadPoolExecutor(max_workers=1) as executor:
future = executor.submit(test, 1)
print(future.result())Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
21CTO
21CTO (21CTO.com) offers developers community, training, and services, making it your go‑to learning and service platform.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
