CodePark/ questions / I/O models, file descriptors & syscalls
START PRACTICINGPRACTICE
Backend question · OS, networking & Linux › I/O models, file descriptors & syscalls

Explain blocking I/O, non-blocking I/O, I/O multiplexing and asynchronous I/O.

These are four ways a program can wait for input and output (I/O).

Blocking I/O: a call like read() puts the thread to sleep until data is ready. So one thread handles one connection at a time.

Non-blocking I/O: the descriptor has the O_NONBLOCK flag set. Now read() returns at once with EAGAIN when no data is ready. But checking every socket again and again in a loop wastes CPU.

I/O multiplexing solves that problem. One thread asks the kernel to watch many descriptors. The thread sleeps until at least one of them is ready. The calls for this are:

  • select and poll
  • epoll on Linux
  • kqueue on BSD and macOS

The thread then does non-blocking reads on the ready descriptors only. This is the readiness model. Node's libuv uses it for sockets.

Asynchronous I/O goes one step further. You submit an operation, and the kernel does the whole thing. It tells you when the data is already in your buffer. Examples are io_uring on Linux and IOCP on Windows. This is the completion model.

In short: readiness means "you can read now". Completion means "the read is done".

  • Blocking I/O: the thread sleeps until the operation completes.
  • Non-blocking I/O: the call returns EAGAIN at once when data is not ready.
  • Multiplexing: one thread waits on many file descriptors with select, poll, epoll or kqueue.
  • Async I/O: the kernel does the operation and tells you when it is complete (io_uring, IOCP).
  • Node's libuv uses readiness-based multiplexing for sockets.