137 / 163 · C11 · 8 min
Event-based Concurrency (Advanced)
This chapter presents a way to build concurrent servers without threads. The program centers on an event loop that processes one arriving event at a time, giving the developer full scheduling control and removing any need for locks. The essential restriction is that handlers must never perform operations that can block.
In this lesson
How the Event Loop Operates
The server enters a never-ending loop. Each iteration first gathers the currently pending events and then invokes the matching handler for each of them in turn. A handler performs only a small amount of work before returning, so the loop always decides what runs next. The developer therefore chooses the order of tasks directly instead of leaving that choice to the operating-system scheduler.
Watching Multiple Descriptors with select
select lets a program ask, in one call, whether any of a set of file descriptors is ready for reading, writing or has an error. The caller supplies the interesting descriptors and an optional timeout. On return the sets contain only the descriptors that are truly ready, so the loop processes just those descriptors and neither spins nor blocks unexpectedly.
Single-Threaded Execution Removes Locks
The whole program has only one flow of execution, so at most one handler is running at any instant. Shared data cannot be accessed concurrently, therefore mutexes, condition variables and the deadlocks and races they introduce all disappear. The programmer can concentrate on the application logic instead of on synchronization details.
A Blocking Call Freezes the Whole Loop
The moment any handler issues a system call that must wait for disk or network, the loop stops there and every later event goes unserved. The only way to stay responsive is to make every I/O operation a non-blocking interface that returns immediately; the real wait happens in the background and is later delivered to the loop as a fresh event.
Pitfalls
- Calling a blocking open or read inside a handler makes the entire server unresponsive
- Failing to inspect every ready descriptor after select misses events
Run an example
Minimum C11 · complete program · Download .c
#include <stdio.h>
#include <stdbool.h>
int main(void) {
bool ready[2][4] = {
{true, false, true, false},
{false, true, false, true}
};
const char *names[4] = {"listen", "clientA", "clientB", "disk"};
printf("Starting simulated event loop\n");
for (int round = 0; round < 2; round++) {
printf("Round %d:\n", round + 1);
for (int fd = 0; fd < 4; fd++) {
if (ready[round][fd]) {
printf(" Handling event on %s (fd %d)\n", names[fd], fd);
}
}
}
printf("Event loop finished without blocking\n");
return 0;
}
Compile locally
gcc -std=c11 -Wall -Wextra -Wpedantic -Werror ostep-33-event-based.c -o example && ./exampleExpected result
Starting simulated event loop
Round 1:
Handling event on listen (fd 0)
Handling event on clientB (fd 2)
Round 2:
Handling event on clientA (fd 1)
Handling event on disk (fd 3)
Event loop finished without blocking
CHECK YOUR UNDERSTANDING
Close the answer. Explain it.
Why does an event-driven server on a single CPU need no locks when touching shared data?
Show a reference answer
Because only one handler executes at a time, two flows of execution can never read or write the same data simultaneously.
Check the sources
Drafts and official chapters change. The version mark is only the example’s minimum.