Lab 04: Concurrency

Objective

Write concurrent Python programs: threading, multiprocessing, concurrent.futures, thread-safe data structures, and the GIL's implications.

Time

35 minutes

Prerequisites

  • Lab 02 (Decorators)

Tools

  • Docker image: zchencow/innozverse-python:latest


Lab Instructions

Step 1: Threading Basics & the GIL

docker run --rm zchencow/innozverse-python:latest python3 -c "
import threading
import time

def task(name: str, duration: float, results: list):
    print(f'  [{name}] starting')
    time.sleep(duration)
    results.append(f'{name} done in {duration}s')
    print(f'  [{name}] finished')

# Sequential
print('=== Sequential ===')
start = time.perf_counter()
results = []
for name, dur in [('A', 0.05), ('B', 0.03), ('C', 0.04)]:
    task(name, dur, results)
print(f'Sequential: {time.perf_counter()-start:.2f}s')

# Parallel with threads (good for I/O-bound work)
print()
print('=== Threaded ===')
start = time.perf_counter()
results = []
threads = []
for name, dur in [('A', 0.05), ('B', 0.03), ('C', 0.04)]:
    t = threading.Thread(target=task, args=(name, dur, results))
    threads.append(t)
    t.start()

for t in threads:
    t.join()
print(f'Threaded: {time.perf_counter()-start:.2f}s')
print('Results:', results)

# GIL means threads don't help for CPU-bound work
def cpu_task(n: int) -> int:
    return sum(i**2 for i in range(n))

start = time.perf_counter()
results = [cpu_task(100_000) for _ in range(4)]
print(f'CPU sequential: {time.perf_counter()-start:.3f}s, result={results[0]:,}')
"

💡 Python's GIL (Global Interpreter Lock) means only one thread executes Python bytecode at a time. Threads are great for I/O-bound work (network, disk) where threads wait — but for CPU-bound work (computation), use multiprocessing to bypass the GIL with separate processes.

📸 Verified Output:


Step 2: Thread Safety — Locks & Thread-Safe Structures

📸 Verified Output:


Steps 3–8: ThreadPoolExecutor, ProcessPoolExecutor, Semaphore, Event, Barrier, Capstone

📸 Verified Output:


Summary

Tool
Best for
GIL?

threading.Thread

I/O-bound, simple concurrency

Limited by GIL

ThreadPoolExecutor

I/O-bound pool, map/submit

Limited by GIL

ProcessPoolExecutor

CPU-bound, bypasses GIL

No GIL

threading.Lock

Protect shared mutable state

threading.Semaphore

Limit concurrent access

threading.Event

Signal between threads

queue.Queue

Thread-safe producer/consumer

Further Reading

Last updated