ASSIGNMENT
on
“Threading in Python”
Course Code: PROG 301
Submitted by:
Istiaque Ahmed
ID NO. 2201027 of 2022-2023
4th Semester, B.Sc.
Submitted to:
Farzana Akter
Assistant Professor
Department of IoT and Robotics Engineering,
Gazipur Rahman Digital University
Kaliakoir, Gazipur
Threading in Python, the Role of the GIL, and
Alternatives
1) Threading in Python
• Definition: Threading is a way of running multiple parts of a program (called
threads) at the same time within a single Python process.
• Shared memory: All threads share the same memory space, which makes
communication between them easier compared to separate processes.
• Best use case: Threading works best for I/O-bound tasks (like downloading files,
reading from disk, or making network requests) where the program spends time
waiting.
Example (I/O-bound):
Without threads this would take 4 seconds; with threads it finishes in ~2 seconds because
waiting overlaps.
2) The Global Interpreter Lock (GIL)
• What it is: In CPython (the main Python interpreter), there is a mechanism called the
Global Interpreter Lock (GIL).
• How it works: The GIL only allows one thread to execute Python bytecode at a
time, even if the computer has many CPU cores.
• Impact: This means threads cannot achieve true parallelism for CPU-heavy work.
They take turns running Python code.
• Good for: I/O-bound tasks (where waiting happens).
• Bad for: CPU-bound tasks (like mathematical calculations).
Example (CPU-bound):
Even with 2 threads, the time is almost the same as running once, because of the GIL.
3) Alternatives to Overcome GIL Limitations
a) Multiprocessing
• Each process has its own Python interpreter and its own GIL, so they can run truly
in parallel on multiple CPU cores.
• Best for CPU-heavy work.
from multiprocessing import Process
import time
def count():
total = 0
for i in range(50_000_00):
total += i
p1 = Process(target=count)
p2 = Process(target=count)
start = time.time()
p1.start(); p2.start()
p1.join(); p2.join()
end = time.time()
print("Time with processes:", end - start)
b) Optimized Libraries (NumPy, Pandas, etc.)
• Many scientific libraries are written in C/C++ and release the GIL while performing
heavy calculations.
• They allow Python code to indirectly use multiple cores.
import numpy as np
a = np.arange(10_000_0)
b = a * a # very fast, no GIL issue
c) Asyncio
• Provides concurrency using an event loop instead of threads.
• Best for handling thousands of I/O tasks at once.
import asyncio
async def worker(name):
print(f"Starting {name}")
await asyncio.sleep(2)
print(f"Finished {name}")
async def main():
await asyncio.gather(worker("Task-1"), worker("Task-2"))
asyncio.run(main())
d) Other options:
• Cython / Numba (compile Python code and release GIL)
• External programs via subprocess (e.g., call ffmpeg for video tasks)
• Distributed frameworks like Dask or Ray for very large workloads