threading vs multiprocessing vs asyncio

Read the explanation
What if

Two jobs, each a network fetch and then CPU work. With threads, the fetches overlap but the CPU work takes turns on the GIL.

Predict first

Two threads each wait on the network, then crunch numbers in pure Python. Which part overlaps?

0123456789main: start 2 threadsstartjoin()returnfetch: time.sleepcrunchthread a: waits for the GILwaitsthread a: crunch, donedonefetch: time.sleepthread b: waits for the GILwaitscrunchthread b: waits for the GILwaitsthread b: crunch, donedone

Tap any block to see what that thread is doing.

  • Running
  • Waiting / parked
  • Waiting on I/O
  • Blocked
Step 1/5

main starts two threads. Each fetches from the network, then crunches numbers.

1def crunch():2    total = 03    for i in range(5_000_000):  # pure Python CPU work4        total += i5    return total6 7def job(name):8    print(f"{name} fetch")9    time.sleep(0.1)  # waiting on the network: the GIL is free10    print(f"{name} crunch")11    crunch()  # needs the GIL the whole time12    print(f"{name} done")13 14def main():15    threads = [threading.Thread(target=job, args=(n,)) for n in "ab"]16    for t in threads:17        t.start()18    for t in threads:19        t.join()20 21if __name__ == "__main__":22    main()

Explanation

What just happened

Each job waits on the network, then crunches numbers in pure Python. With threads, both fetches waited at once: a thread waiting on I/O lets go of the GIL. Crunching needs the GIL, so the threads took turns on one core.

Try the what-if switch

Which one to pick

Waiting on I/O Python CPU work
threading overlaps takes turns (GIL)
multiprocessing overlaps runs in parallel
asyncio overlaps one task at a time

Mostly waiting: asyncio or threads. Mostly crunching: multiprocessing, or a free-threaded build.

Common questions

When should I use threading, multiprocessing or asyncio?

Threads or asyncio when the work is mostly waiting on I/O: both overlap the waiting. multiprocessing for pure-Python CPU work: each process has its own GIL.

Do Python threads run in parallel?

Not on the standard build. Only the thread holding the GIL runs Python code, so two CPU-bound threads share one core. Waiting on I/O releases the GIL, so waits overlap.

Is asyncio faster than threading?

Neither speeds up CPU work. asyncio runs on one thread, and its tasks switch only at an await: the order is predictable, but a blocking call stalls every task.

Verified: this timeline matches a real program, checked in CI on Python 3.14, 3.15 and free-threaded 3.15t. How we verify. Unclear or wrong? Tell us