What are Actors? Well, they’re kind of like threads, with messaging baked-in. But in many ways, they’re more like objects, because they hold state and communicate using messages. They’re kinda like… threabjects… or objeads. But for the non-kindergardners among us, Actors are a good enough name.
Actors are an approach to concurrency which has proven remarkably successful in languages like Erlang and Scala. They emphasize message passing as the only means of exchanging state, as opposed to threaded approaches like mutexes, conditions, and semaphores which hopefully guard access and mutation of any shared state, emphasis on the hopefully. Using messaging eliminates several problems in multithreaded programming, including many types of race conditions and deadlocks which result from hope dying in the cold light of reality.
The basic operation of an Actor is easy to understand: like a thread, it runs concurrently with other Actors. However, unlike threads it is not pre-emptable. Instead, each Actor has a mailbox and can call a routine named “receive” to check its mailbox for new messages. The “receive” routine takes a filter, and if no messages in an Actor’s mailbox matches the filter, the Actor sleeps until it receives new messages, at which time it’s rescheduled for execution.
Well, that’s a bit of a naive description. In reality the important part about Actors is that they cannot mutate shared state simultaneously. That means there are no race conditions or deadlocks because there are no mutexes, conditions, and semaphores, only messages and mailboxes.
Because Actors sleep while waiting for interesting events to occur and show up in their mailbox, they can easily be used in the same way as blocking I/O operations in a traditional program. Every TCP socket created through the Revactor API has read and write methods which work just like the ones in the traditional Ruby Socket API. The Actor socket just requests that it be notified when socket events occur, then sleeps until they happen.
However, Actors aren’t just intended as just a drop-in replacement for traditional Ruby sockets. They allow you to start structuring your programs as networks of interconnected components which communicate using message passing. This approach to structuring your programs is extremely powerful, as it facilitates a simple and lock-free way to build services that allow concurrent access to complex data structures.
That guy asks: “Okay, sure, I’ve used interthread message queues before. So what’s the big freakin’ deal?”
Threads that communicate with messages are a great start to building robust concurrent programs. But how well have you thought through how you receive messages?
Actors give you a mailbox and a toolbox to apply filters to it. This assures that the present operation only consumes messages it’s interested in and other operations can run in the background, sending you messages to inform you of the present status. Those message linger in the mailbox until they’re ready to be acted upon.
This means tons of events can be flying around in the background. When they complete, they just send the interested Actor a message. The mailbox provides a simple way of synchronizing all that concurrency flying around in the background and operating on it based on what’s in your mailbox.
If that idea doesn’t slap you in the face and scream “DUH!”, I suggest you watch the eerily related video RSS in Plain English. If you’re a traditional threaded programmer, your threads go all over the place to access and mutate data. But this process is cumbersome, error-prone, and wastes valuable time. How many times do you have to make sure your mutexes, conditions, and semaphores have been implemented correctly, and how many times have you debugged deadlocks when using these concurrency primitves? It’s annoying and tedious…
Like e-mail, or RSS, Actors give you a mailbox: a one-stop shop where everything of interest shows up. Actors apply filters to their mailbox to find the items they’re presently interested in. Is this sounding eerily familiar to the e-mail and RSS addicts out there in Intarweb land? How many times a day does your brain call “receive”?
I’m sure most people reading this realize what things like e-mail and RSS did to your life. Imagine what it could do to your concurrent programs…
An important part of using a mailbox to synchronize concurrency is being able to filter only what you’re immediately interested in and leave the rest around to handle at a later time. In order to do that you need to be able to effectively filter the messages sitting in your mailbox.
Ruby provides some powerful tools for filtering data. One of these is the = expression. You may have never used it directly, but you've probably used it indirectly, such as writing a case statement. Ruby uses the = expression to match against every case in a case expression. You can use it to compare a regular expression against a string. You can use it to compare a class against an object to determine if it’s an instance of that class. And you can use it for a lot more…
Revactor uses the Case gem (written by MenTaLguY) to perform comparisons on collections. This means that you can extend the = expression to multiple members of an array (or tuple). Revactor's filter can take a Case expression with a list of members to compare against, ala Case[:foo, :bar, :baz]. This is great if you want to match a specific list of terms, but what if you want to match a list with wildcards for certain members? Well, since = compares case, you can always use Object as a wildcard, because everything is an Object in Ruby. So Case[:foo, Object, Object] will also match[:foo, :bar, :baz] with ===, and thus with the Filter object.
Perhaps === isn’t enough? Do you have some complex criteria to match against? Well, the Case gem includes a Case::Predicate matcher. This takes a block, passes the block the member in question evaluates it for truthiness, and returns the result.
Actors rule. You really should use them in your programs. Especially if your programs do a lot of stuff at once. Seriously, whatever you’re doing besides Actors, it probably sucks. Actors are this awesome panacea that will make it all better, I swear. In conclusion: use them, do it!