eventfd is a Linux file descriptor backed by a 64-bit kernel counter. It turns an application notification into file-descriptor readiness, so an event loop can wait for “socket has bytes” and “another thread queued work” through the same polling mechanism.
Prerequisites
Read IO Selectors first. epoll explains readiness notification.
The Problem
An epoll worker usually sleeps in epoll_wait. Another thread may still need to wake it:
- an acceptor thread accepted a new socket and assigned it to this worker
- a disk worker finished a blocking file read and queued a result for this worker
- a control thread asked the worker to shut down or reload state
Pushing an item into a mutex-protected queue leaves epoll_wait asleep, because epoll_wait only watches file descriptors. eventfd gives the producer a descriptor it can write and the worker a descriptor it can watch.
The Counter
eventfd(initval, flags) creates a file descriptor whose kernel state is a counter.
int wake_fd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);The initial value here is 0, so the descriptor starts unreadable. EFD_NONBLOCK means reads from an empty counter return EAGAIN instead of blocking. EFD_CLOEXEC closes the descriptor across exec, so child programs do not accidentally inherit it.
Writing adds to the counter:
eventfd_write(wake_fd, 1);eventfd_write is a C helper from <sys/eventfd.h>. It writes an unsigned 64-bit value to the descriptor. In this pattern, writing 1 means “make the counter nonzero so the event loop wakes.”
Reading consumes the counter:
uint64_t value;
eventfd_read(wake_fd, &value);Without EFD_SEMAPHORE, one successful read returns the current counter value and resets the counter to zero. With EFD_SEMAPHORE, one successful read returns 1 and decrements the counter by one.
Readiness
An eventfd works with epoll because the counter controls readiness:
| Counter state | epoll readability |
|---|---|
counter == 0 | Not readable. |
counter > 0 | Readable. |
The worker registers wake_fd like any other descriptor:
struct epoll_event ev = {
.events = EPOLLIN,
.data.ptr = &wake_item,
};
epoll_ctl(epfd, EPOLL_CTL_ADD, wake_fd, &ev);After another thread calls eventfd_write(wake_fd, 1), epoll_wait(epfd, events, 64, -1) returns an event for wake_fd.
Wakeup Pattern
The usual pattern is “queue first, write to eventfd second.”
enqueue_fd(worker, conn_fd);
eventfd_write(worker->wake_fd, 1);In this snippet, conn_fd is the connected client socket descriptor returned by accept. enqueue_fd is a project-local helper that puts that socket descriptor into the target worker’s queue.
The worker handles the wakeup by draining the eventfd counter, then draining the real work queues.
static void drain_wake_fd(struct worker *w) {
uint64_t value;
for (;;) {
int rc = eventfd_read(w->wake_fd, &value);
if (rc == 0)
continue;
if (errno == EAGAIN)
return;
die("eventfd_read");
}
}The eventfd counter is not the work queue. Multiple writes may coalesce into one readable event, and one read may drain a counter value greater than one. The queue remains the source of truth:
drain_wake_fd(w);
register_new_connections(w);
process_completed_file_jobs(w);The server design does not need EFD_SEMAPHORE. The worker only needs to know that work may be waiting. After the wakeup, it checks the queues directly.
Where It Fits
eventfd is not part of epoll; it is a separate Linux descriptor type. epoll can watch it because it exposes readiness. These descriptors in a multi-threaded server have different jobs:
| Descriptor | Created by | Job |
|---|---|---|
listen_fd | socket / bind / listen | Accept new client sockets. |
epfd | epoll_create1 | Hold the worker’s epoll interest and ready lists. |
wake_fd | eventfd | Wake a worker when another thread queued work. |
conn_fd | accept | Communicate with one client connection. |
Only epfd is the epoll instance. wake_fd, listen_fd, and conn_fd are descriptors that can be registered inside an epoll instance.
See also
Sources
- Linux man-pages:
eventfd(2),eventfd_read(3),eventfd_write(3), andepoll(7).