In the GIL problem, two threads computing factorial ran no faster than one — the GIL serializes CPU-bound work. But for I/O-bound work, Python has a better tool: asyncio.
asyncio uses a single-threaded event loop and cooperative multitasking. When a coroutine hits await, it suspends and yields control back to the event loop, which can run another coroutine. There's no thread switching, no GIL contention — just voluntary pauses at await points.
Implement two functions:
make_task(delay_secs: float) — an async function that sleeps for delay_secs seconds using asyncio.sleep. Returns a coroutine when called.gather_tasks(n: int, delay_secs: float) -> float — creates n tasks using make_task, runs them concurrently with asyncio.gather, and returns the total elapsed time in milliseconds.Constraints:
make_task must be declared with async def and use await asyncio.sleep(...)gather_tasks must use asyncio.gather to run tasks concurrentlythreading, multiprocessing, or any other concurrency primitivegather_tasks is a regular synchronous function (not async) — use asyncio.run(...) inside it