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
dockerrun--rmzchencow/innozverse-python:latestpython3-c"import threadingimport timedef 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')# Sequentialprint('=== 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 workdef 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.