Recently I read up on Wikipedia about Nginx. What puzzled me was this paragraph:

Nginx uses an asynchronous event-driven approach to handling requests, instead of the Apache HTTP Server model that defaults to a threaded or process-oriented approach, where the Event MPM is required for asynchronous processing. Nginx's modular event-driven architecture[18] can provide more predictable performance under high loads.

Now I understand how both approaches, event-driven and threaded/process-oriented, work. But what I find confusing about this statement is that even asynchronous approaches need to delegate their work (once received) to some dedicated task/thread/process.

Or am I wrong?


Every server that processes requests in parallel and shares a source of input (e.g., a port) has to delegate work somehow. If you don't process requests in parallel, you have a single-threaded service; if you don't share a source of input, you have an array of single-threaded services. The difference between the two models has more to do with how the work is carried out than how it's delegated.

The process- or thread-per-task model simplifies servicing a request because you can treat it as a single, linear task that runs from start to finish and stops when it needs to wait for something to happen. The trade-off is that any time the task blocks or the OS decides its time slice is up, there has to be a context switch between what was running and what will be. Every cycle you spend doing a context switch is a cycle you can't spend doing work, and there may be several of them involved in servicing a single request.

Programs that bill themselves as event-driven or asynchronous tend to treat work as a unit of data (i.e., a structure with information about the task at hand) rather than a unit of execution (i.e., a process or thread). That means putting running worker threads on multiple cores that pull work units from a queue, handle some part of the job as described by the data and set it aside until the next thing happens. When the next thing does happen, whatever detected it just updates the task's information and puts it back on the queue to be handled.

Doing this reclaims all of the managing of state that was being done by the OS and puts it in the hands of the program. The worker ends up a bit more complex, but because the state management can be tailored for the application, it ends up being a lot less ham-fisted (and therefore less expensive) than the wholesale register-and-stack swap you get during a context switch. The OS can still force a context switch or the thread can voluntarily yield if it runs out of work, but either of those is a much-less-common event than the handful of them you'd get per request in the per-thread model. This is a bit of an oversimplification, as there are other things you can do within this model to get even more out of the processors you have, but they're outside the scope of this answer.

That said, it is possible to write an event-driven program that uses the thread-per-task model, and it will have all of the same inefficiencies.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.