Skip to content

How does one thread watch a thousand sockets?

Level: 201 · anyone who wants to know what the event loop is waiting on

Stub — a question and its expected answers, not yet a lesson. No program behind this page has been written or run, so nothing on it has been through the check that backs every finished page. The table is what the finished lesson is expected to find, and every cell is unverified until a program prints it. (Not machine-checked here.)

One line: Under every event loop is one system call — poll, epoll_wait, kqueue — that takes a set of file descriptors and sleeps until one is ready, which is how one thread serves a thousand idle connections at no cost per connection; a thread per connection pays a stack each, and the two designs meet in the middle when the connections are busy rather than idle.

The question

A thousand client sockets, one message each, spread over a second. Serve them with a thread each, and with one thread and poll. The page counts the threads, the memory and the time for both, as Real runs, and shows the system call under the loop in the languages that let you see it — then shows the case that flips the result: every connection busy at once.

What each language is expected to say

Expected answer, to be verified by the program
Rust mio wraps epoll/kqueue for tokio (link, not run); the std column is a thread per connection
Go net.Listen and a goroutine per connection, with the runtime's netpoll underneath doing the epoll
C poll(2) over an array of descriptors, the loop written out
C++ the same; or boost::asio (link, not run)
Java java.nio.channels.Selector; or a virtual thread per connection over the same NIO
Python selectors.DefaultSelector, which asyncio uses underneath

What the programs have to show

  • a thousand connections served both ways, as Real runs: threads, RSS, time
  • the CI example at ten connections: every message answered, both ways
  • strace -c or dtruss counting the epoll_wait/kevent calls under the loop, as Real runs

See also