Continuations
A stateful actor's code is processed in chunks, separated by quiet periods of waiting for new events (messages). This can be naturally modeled through continuations. However, since JVM doesn't support continuations directly, they have to be simulated in the actor frameworks, which has a slight impact on organization of the actors' code.
Calling the react() method from within the actor's code has slightly different semantics than just reading the next message from the actor's inbox. Under the covers, the closure supplied as a parameter to the react method is scheduled for processing once a message becomes available. The scheduling is the last work the current thread does on the actor's behalf. Once scheduled, the thread is detached from the actor and returned to the pool to serve other actors.
To allow detaching actors from the threads, the react() method requires that the code to be written in a special "Continuation-style."
def myActor = Actors.actor { loop { react {msg1 -> ... react {msg2 -> ... } // Never reached } // Never reached } // Never reached }
Essentially, react() schedules the supplied code (closure) to be executed upon next message arrival and quits the actor body. The closure supplied to the react() methods is the code where the computation should continue. Thus, we have continuation style.
The loop() method allows iteration within the actor body. Unlike typical looping constructs, loop() cooperates with nested react() blocks and ensures looping across subsequent message retrievals.
Stateful Actors Used from Java
You may see that stateful actors use nested closures heavilly to organize their bodies. Although it is possible to encode a nested actor's body in Java using anonymous inner classes for react's parameters, sticking to Groovy can save you a lot of typing and considerably reduce verbosity of the code.
Active Objects
Active objects give actors an OO facade, allowing you to program in a more traditional OO style. You avoid dealing directly with the actor machinery, sending messages, defining message handlers and sending or waiting for replies.
import groovyx.gpars.activeobject.ActiveObject import groovyx.gpars.activeobject.ActiveMethod @ActiveObject class Decryptor { @ActiveMethod def decrypt(String encryptedText) { return encryptedText.reverse() } @ActiveMethod def decrypt(Integer encryptedNumber) { return -1*encryptedNumber + 142 } } final Decryptor decryptor = new Decryptor() def part1 = decryptor.decrypt(' noitcA ni yvoorG') def part2 = decryptor.decrypt(140) def part3 = decryptor.decrypt('noittide dn') print part1.get() print part2.get() println part3.get()
As you can see in the code example above, active objects are plain POGOs (Plain Old Groovy Objects) annotated with the @ActiveObject annotation. Where they differ from ordinary objects is their behavior upon method invocation. Leveraging Groovy compile-time code transformations, whenever any of the active methods (marked as @ActiveMethod) are called, GPars translates the method calls to messages being sent to the object's internal actor. The internal (hidden) actor then handles these messages asynchronously by invoking the desired functionality in its own context.
Because the code now runs asynchronously and doesn't block the caller, we need a new way to propagate the return values up to the caller. The mechanism is commonly referred to as "Future" or "Promise." Active methods return such Promises instead of real results, which by themselves are only handles allowing the caller to obtain the result once it is calculated. The part1, part2 and part3 variables in the preceding code example above are these "Promises." The script calls their get() method to retrieve the return value.
Because this mechanism relies on compile-time meta-programming, active objects must be implemented in Groovy. However, once created they can be invoked from Java just like from Groovy.
Summary
GPars brings together several handy concurrency abstractions. This article was dedicated to the actors concept, which is (along with Communicating Sequential Processes (CSP) and dataflow) one of the popular message-passing paradigms. We have seen how actors are created and invoked both in Groovy and in Java and how active objects can be used to give actors a familiar OO face.
GPars toolset
Because there is no single answer to all the concurrency challenges, GPars provides more than just actors, It includes:
- Agents
- Dataflow concurrency
- Communicating Sequential Processes
- Parallel collections
- Fork/Join capabilities
- Composable asynchronous functions
This wide range of operations enables developers to pick the best tool for their job. Going into details of each of these abstractions is, however, beyond the scope of this article. The best way to see GPars in its entirety is to check out the GPars User Guide.
Václav is a programming enthusiast who's constantly seeking ways to make development more effective and enjoyable. He's particularly interested in server-side Java technologies, distributed systems and concurrency. You can check out his blog or follow him on twitter.