Threads vs Async vs Parallel
- Synchronous: Tasks will run one after another, each blocks until finished
- Threading (multithreading): Multiple folwos of execution inside one process share the same memory
- Async: A single thread co-operatively runs may coroutines switching at await points
- Parallelism: Multiple process on multiple CPU Cores, Each has its own memory interface
from concurrent.futures import ProcessPoolExecutor
import math, time
def cpu_bound(n):
print(f"start {n}")
s = sum(math.sqrt(i) for i in range(100000000))
print(f"s = {s}")
print(f"End {n}")
return n
if __name__ == "__main__":
start = time.time()
with ProcessPoolExecutor() as ex:
results = list(ex.map(cpu_bound, range(4)))
print("Took ", round(time.time() -start, 2), " Seconds")
- When to use What
| Workload Type | Sync | Threads | Async | Processes |
|---|---|---|---|---|
| I/O Heavy (HTTP, DB, Disk) | No | yes | Better | yes |
| CPU-heavy (math, ML) | No | No | No | Better |
| Realtime (Chat, Websocket) | No | NO | Async | NO |
| Simple Scripting | Better | No | No | No |
Async IO in python
-
Where we will use AsyncIO
- ORMs
- Fast API
- AI Agents
-
Why async exists
- many programs wait (network, disk, sleep), while waiting cpu remains idle
- Async lets one thread cooperatively juggle many waiting jobs, when one awaits I/O, another runs
- It gives concurrency for I/O bound work
-
Three words:
- Awaitable: Anything you can await (a corouting or a Task)
- Coroutine: A function defined with async def that can be awaited.
- Task: A scheduled coroutine managed by the event loop
-
Event Loop:
- A loop that drives all asynch work: it schedules, paused at await, resumes when ready
- Entrypoint:
asyncio.run(main())creates a loop, runs the main and closes the loop
import asyncio
async def main():
print("hello")
await asyncio.sleep(2)
print("world")
asyncio.run(main())
- Coroutine: Creating and awaiting
import asyncio
async def fetch_one(n):
print(f"start {n}")
await asyncio.sleep(1)
print(f"done {n}")
return n * n
async def main():
a = await fetch_one(1)
b = await fetch_one(2)
print(a, b)
asyncio.run(main())
- Task: Scheduling multiple coroutines concurrently
import asyncio
import time
async def fetch_one(n):
print(f"start {n}")
await asyncio.sleep(n)
print(f"done {n}")
return n * n
async def main():
t1 = asyncio.create_task(fetch_one(5))
t2 = asyncio.create_task(fetch_one(3))
r1 = await t1
r2 = await t2
print(r1, r2)
start = time.time()
asyncio.run(main())
print("Took ", round(time.time() -start, 2), " Seconds")
- Gather many tasks
import asyncio
import time
async def fetch_one(n):
print(f"start {n}")
await asyncio.sleep(n)
print(f"done {n}")
return n * n
async def main():
# t1 = asyncio.create_task(fetch_one(5))
# t2 = asyncio.create_task(fetch_one(3))
# r1 = await t1
# r2 = await t2
# print(r1, r2)
results = await asyncio.gather(*(fetch_one(i) for i in range(1,6)))
print(results)
start = time.time()
asyncio.run(main())
print("Took ", round(time.time() -start, 2), " Seconds")
