I’ve seen walking cliches before. There was this one time in the Skyway that I actually saw a guy with a white cane being led by a woman with huge dark sunglasses and a guide dog. Today, though, I realized I was watching a design pattern played out with people instead of objects.
I’ve used the Reactor pattern in my software before. It’s particularly helpful when you combine it with non-blocking multiplexed I/O, such as Java’s NIO package.
Consider a server application such as a web server or mail transfer agent. A client connects to a socket on the server to send a request. The server and client talk back and forth a little bit, then the server either processes or denies the client’s request.
If the server just used one thread, then it could only handle a single client at a time. That’s not likely to make a winning product. Instead, the server uses multiple threads to handle many client connections.
The obvious approach is to have one thread handle each connection. In other words, the server keeps a pool of threads that are ready and waiting for a request. Each time through its main loop, the server gets a thread from the pool and, on that thread, calls the socket "accept" method. If there’s already a client connection request waiting, then "accept" returns right away. If not, the thread blocks until a client connects. Either way, once "accept" returns, the server’s thread has an open connection to a client.
At that point, the thread goes on to read from the socket (which blocks again) and, depending on the protocol, may write a response or exchange more protocol handshaking. Eventually, the demands of protocol satisfied, the client and server say goodbye and each end closes the socket. The worker thread pulls a Gordon Freeman and disappears into the pool until it gets called up for duty again.
It’s a simple, obvious model. It’s also really inefficient. Any given thread spends most of its life doing nothing. It’s either blocked in the pool, waiting for work, or it’s blocked on a socket "accept", "read", or "write" call.
If you think about it, you’ll also see that the naive server can handle only as many connections as it has threads. To handle more connections, it must fork more threads. Forking threads is expensive in two ways. First, starting the thread itself is slow. Second, each thread requires a certain amount of scheduling overhead. Modern JVMs scale well to large numbers of threads, but sooner or later, you’ll still hit the ceiling.
I won’t go into all the details of non-blocking I/O here. (I can point you to a decent article on the subject, though.) Its greatest benefit is you do not need to dedicate a thread to each connection. Instead, a much smaller pool of threads can be allocated, as needed, to handle individual steps of the protocol. In other words, thread 13 doesn’t necessarily handle the whole conversation. Instead, thread 4 might accept the connection, thread 29 reads the initial request, thread 17 starts writing the response and thread 99 finishes sending the response.
This model employs threads much more efficiently. It also scales to many more concurrent requests. Bookkeeping becomes a hassle, though. Keeping track of the state of the protocol when each thread only does a little bit with the conversation becomes a challenge. Finally, the (hideously broken) multithreading restrictions in Java’s "selector" API make fully multiplexed threads impossible.
The Reactor pattern predates Java’s NIO, but works very well here. It uses a single thread, called the Acceptor, to await incoming "events". This one thread sleeps until any of the connections needs service: either due to an incoming connection request, a socket ready to read, or a socket ready for write. As soon as one of these events occurs, the Acceptor hands the event off to a dispatcher (worker) thread that then processes the event.
You can visualize this by sitting in a TGI Friday’s or Chili’s restaurant. (I’m fond of the crowded little ones inside airports. You know, the ones with a third of the regular menu and a line stretching out the door. Like a home away from home for me lately.) The "greeter" accepts incoming connections (people) and hands them off to a "worker" (server). The greeter is then ready for the next incoming request. (The line out the door is the listen queue, in case you’re keeping score.) When the kitchen delivers the food, it doesn’t wait for the original worker thread. Instead, a different worker thread (a runner) brings the food out to the table.
I’ll keep my eyes open for other examples of object-oriented design patterns in real life–though I don’t expect to see many based on polymorphism.