← Concepts

阅读中文版本

Reactor and Proactor Patterns

Reactor demultiplexes readiness events and dispatches handlers that perform the I/O themselves; proactor demultiplexes completion events for I/O the kernel already performed asynchronously.

concurrency

Definition

Reactor is the readiness pattern: an event loop waits on a multiplexer (epoll, kqueue), notifies handlers that a descriptor is ready, and the handler executes the non-blocking read or write. Proactor is the completion pattern: the initiator posts an asynchronous operation with buffers, the OS completes it, and the completion dispatcher invokes the handler with results. POSIX reactors dominate Linux; Windows IOCP is proactor-native.

Context and scope

Node.js and nginx are reactor-shaped; Windows IOCP, Boost.Asio async operations, and io_uring lean proactor. The choice shapes buffer ownership and error semantics.

Common misconceptions

  • Reactor and proactor are synonyms — reactor notifies readiness, you do the I/O; proactor notifies completion, the kernel did the I/O
  • Linux cannot do proactor — io_uring brings true asynchronous completion semantics to Linux, beyond readiness polling
  • The pattern dictates throughput — buffer management and handler duration dominate; the pattern sets who owns buffers and where errors surface

Anti-patterns

  • Doing heavy computation inside reactor handlers and starving the event loop
  • Reusing completion buffers after posting an asynchronous write and racing the kernel
  • Porting reactor code to a proactor API without rethinking buffer ownership and lifetime

Related concepts

Related examples

Authoritative source

https://en.wikipedia.org/wiki/Reactor_pattern

What it is

Two answers to the same question: how does one thread manage thousands of I/O operations. Reactor waits for readiness and delegates the action; proactor initiates the action with its buffers and waits for the kernel to report it done.

How it works

Reactor: register handlers per descriptor; on readiness, dispatch; handler reads or writes non-blocking, handling EAGAIN and partial transfers itself. Proactor: post operation plus buffer; kernel DMA fills or drains it; completion queue delivers results; handler processes finished data. io_uring unified submission and completion rings make proactor-style programming natural on Linux.

When it matters

Designing network runtimes and understanding buffer lifetimes: reactor handlers own buffers during the call, while proactor buffers are pinned from post until completion — an ocean of subtle lifetime bugs for the unprepared. The pattern choice also decides where errors surface and how cancellation propagates.

Pitfalls

Reactor loops serialize — one slow handler delays every other ready descriptor, so offload compute. Proactor buffer reuse before completion corrupts in-flight data; cancellation semantics differ per platform and often leave operations completing anyway. Error reporting splits: readiness errors on dispatch, completion errors in results, and half of the ecosystem documentation conflates them.