Originally, the way `php-fpm` works is pretty much how the entire web worked. Every request into a server would go through `inetd`, it would fork a handler process, and the request would respond when the process exited. This became very difficult to handle at scale, mostly because of the overhead for forking a process, so the next logical step is to have a pool of worker processes ready to go in order to immediately receive requests. This is great for applications that don't need to get redeployed very often, but as we moved into more of a continuous deployment workflow, it began to break down as worker processes would need to be restarted, going back to that whole "overhead" thing. Unicorn suffers from this problem a bit, if you try to `SIGHUP` and reload application code when workers are still processing requests, Unicorn will wait until those processes have finished responding to restart the process and load the new version. If a client is taking too long and hanging onto the process, it's very possible that the process will just never reload the code and you'll have weird errors happening every so often.
A solution to this problem is to move the worker pool model into threads, which allow the server to control a bit more about how the application code is reloaded, and not have to deal with the overhead of forking processes. I believe that's how NGINX, Node.js, Puma, et. al. work under the hood...there's a thread pool of workers and another thread that listens to requests. Everything is event-driven, so when a new event comes in, the listener thread just sends that event off to the pool of worker threads. Basically the same idea, but using an event loop as a model for better concurrency support.
What other server thread models exist? I know php-fpm gives each request it’s own process, but I can’t think of any other feasible strategies off the top of my head.