Threads for I/O-Bound Versus CPU-Bound Work

import threading, time

def cpu_task():
    total = 0
    for i in range(50_000_000):
        total += i

def io_task():
    time.sleep(1)

Scenario 1: start four threads, each running cpu_task(). Scenario 2: start four threads, each running io_task(). Compare each scenario's wall-clock time against running the same four calls one after another in a single thread. In CPython, which scenario sees a real, meaningful speedup from using threads?

A

Neither scenario. The GIL forces every thread to run strictly one at a time from start to finish, so threading never changes wall-clock time in CPython.

B

Scenario 2 only. CPython releases the GIL while a thread is blocked inside time.sleep, so the four sleeps overlap and the four threads finish in about the time one sleep takes. The four cpu_task threads still take turns holding the GIL to run bytecode, so scenario 1 gets little or no speedup, and can even run slightly slower than one thread because of the overhead of repeatedly acquiring and releasing the GIL.

C

Scenario 1 only. CPU-bound work is exactly what threads are for, and the GIL only has an effect on I/O operations.

D

Both scenarios. Once more than one thread is running, CPython schedules them across separate cores regardless of what each thread is doing.