Abstract


  • An event loop is a scheduler used to achieve O. It manages I/O events and, once they are ready, dispatches the corresponding tasks (callbacks, coroutines, futures). While waiting for I/O, it can switch to and execute other tasks that are ready, ensuring efficient concurrency without blocking

Responsive

Suitable for Process (进程) that need to respond to events, such as a graphical user interface (GUI) program.

Event Loop OS-level Flow

  1. Event loop asks the kernel to start an I/O operation (e.g., “read from this socket” or “write to this file”). This is a non-blocking syscall (e.g., epoll_ctl, kqueue, IOCP)
  2. The kernel gives back an I/O handle / file descriptor that can be monitored for readiness (not data itself yet).
  3. The kernel tracks the I/O request internally, basically babysits the device driver queue
  4. Instead of the thread “periodically checking,” the thread calls a multiplexing syscall (like epoll_wait, select, kqueue, IOCP). This blocks the thread efficiently until any registered event becomes ready. So no busy-polling, it’s “sleep until kernel wakes me.”
  5. When something happens (e.g., socket readable, file write complete), the kernel notifies the event loop and posts the event into the syscall’s result set. On some OSes this is via interrupts/upcalls at the driver level, which bubble up to epoll/kqueue
  6. The event loop thread collects these ready events and dispatches the corresponding callbacks, coroutines, or futures until completion

Asynchronous Single Threading


No Parallelism

There is only one single Thread, so can’t take advantage of Multi-core Chip. A CPU Bounded task can block the execution of the entire program, thus no Parallelism (并行性)

Python Event Loop


  • We run our async entrypoint inside an event loop with asyncio.run(coro). On Linux, asyncio’s default loop is SelectorEventLoop, which is mostly Python code using epoll via the selectors module (with C-accelerated Tasks/Futures in CPython)
  • uvloop is a drop-in replacement written in Cython on top of libuv, so it has much lower overhead for scheduling and I/O, and is typically faster for I/O-bound workloads. uvloop makes asyncio 2-4x faster. Guide on integrating into asyncio. It is used by sglang which is a fast serving framework for large language models and vision language models

Gevent

Mixing gevent and asyncio is painful because they’re separate event-loop runtimes. Each wants to own the main thread’s scheduling, sockets/FD registrations, timers, and signal handlers. Gevent’s monkey-patching collides with asyncio’s expectations, causing missed wakeups, stalls, or deadlocks (not just “overhead”). For new code, asyncio + uvloop is the modern, fast, and well-supported path (structured concurrency, rich ecosystem, great perf for I/O).

If you must combine: isolate via separate processes (best) or a dedicated thread without monkey.patch_all(), and communicate over IPC.