Is the Actor model just another name for message passing between threads? In other words, can you consider a Java Thread object with a message queue an Actor? Or is there more to the Actor model? Bartosz investigates.
I’ll start with listing various properties that define the Actor Model. I will discuss implementation options in several languages.
Concurrency
Actors are objects that execute concurrently. Well, sort of. Erlang, for instance, is not an object-oriented language, so we can’t really talk about “objects”. An actor in Erlang is represented by a thing called a Process ID (Pid). But that’s nitpicking. The second part of the statement is more interesting. Strictly speaking, an actor may execute concurrently but at times it will not. For instance, in Scala, actor code may be executed by the calling thread.
Caveats aside, it’s convenient to think of actors as objects with a thread inside.
Message Passing
Actors communicate through message passing. Actors don’t communicate using shared memory (or at least pretend not to). The only way data may be passed between actors is through messages.
Erlang has a primitive send operation denoted by the exclamation mark. To send a message Msg to the process (actor) Pid you write:
Pid ! Msg
The message is copied to the address space of the receiver, so there is no sharing.
If you were to imitate this mechanism in Java, you would create a Thread object with a mailbox (a concurrent message queue), with no public methods other than put and get for passing messages. Enforcing copy semantics in Java is impossible so, strictly speaking, mailboxes should only store built-in types. Note that passing a Java Strings is okay, since strings are immutable.
-Typed messages
Here’s the first conundrum: in Java, as in any statically typed language, messages have to be typed. If you want to process more than one type of messages, it’s not enough to have just one mailbox per actor. In Erlang, which is dynamically typed, one canonical mailbox per actor suffices. In Java, mailboxes have to be abstracted from actors. So an actor may have one mailbox for accepting strings, another for integers, etc. You build actors from those smaller blocks.
But having multiple mailboxes creates another problem: How to block, waiting for messages from more than one mailbox at a time without breaking the encapsulation? And when one of the mailboxes fires, how to retrieve the correct type of a message from the appropriate mailbox? I’ll describe a few approaches.
-Pattern matching
Scala, which is also a statically typed language, uses the power of functional programming to to solve the typed messages problem. The receive statement uses pattern matching, which can match different types. It looks like a switch statements whose case labels are patterns. A pattern may specify the type it expects. You may send a string, or an integer, or a more complex data structure to an actor. A single receive statement inside the actor code may match any of those.
receive { case s: String => println("string: "+ s) case i: Int => println("integer: "+ i) case m => println("unknown: "+ m) }
In Scala the type of a variable is specified after the colon, so s:String declares the variable s of the type String. The last case is a catch-all.
This is a very elegant solution to a difficult problem of marrying object-oriented programming to functional programming–a task at which Scala exceeds.
-Casting
Of course, we always have the option of escaping the type system. A mailbox could be just a queue of Objects. When a message is received, the actor could try casting it to each of the expected types in turn or use reflection to find out the type of the message. Here’s what Martin Odersky, the creator of Scala, has to say about it:
The most direct (some would say: crudest) form of decomposition uses the type-test and type-cast instructions available in Java and many other languages.
In the paper he co-authored with Emir and Williams (Matching Objects With Patterns) he gives the following evaluation of this method:
Evaluation: Type-tests and type-casts require zero overhead for the class hierarchy. The pattern matching itself is very verbose, for both shallow and deep patterns. In particular, every match appears as both a type-test and a subsequent type-cast. The scheme raises also the issue that type-casts are potentially unsafe because they can raise ClassCastExceptions. Type-tests and type-casts completely expose representation. They have mixed characteristics with respect to extensibility. On the one hand, one can add new variants without changing the framework (because there is nothing to be done in the framework itself). On the other hand, one cannot invent new patterns over existing variants that use the same syntax as the type-tests and type-casts.
The best one could do in C++ or D is to write generic code that hides casting from the client. Such generic code could use continuations to process messages after they’ve been cast. A continuation is a function that you pass to another function to be executed after that function completes (strictly speaking, a real continuation never returns, so I’m using this word loosely). The above example could be rewritten in C++ as:
void onString(std::string const & s) { cout << "string: " << s << std::endl; } void onInt(int i) { cout << "integer: " << i << std::endl; } receive<std::string, int> (&onString, &onInt);
where receive is a variadic template (available in C++0x). It would do the dynamic casting and call the appropriate function to process the result. The syntax is awkward and less flexible than that of Scala, but it works.
The use of lambdas might make things a bit clearer. Here’s an example in D using lambdas (function literals), courtesy Sean Kelly and Jason House:
receive( (string s){ writefln("string: %s", s); }, (int i){ writefln("integer: %s", i); } );
Interestingly enough, Scala’s receive is a library function with the pattern matching block playing the role of a continuation. Scala has syntactic sugar to make lambdas look like curly-braced blocks of code. Actually, each case statement is interpreted by Scala as a partial function–a function that is not defined for all values (or types) of arguments. The pattern matching part of case becomes the isDefinedAt method of this partial function object, and the code after that becomes its apply method. Of course, partial functions could also be implemented in C++ or D, but with a lot of superfluous awkwardness–lambda notation doesn’t help when partial functions are involved.
-Isolation
Finally, there is the problem of isolation. A message-passing system must be protected from data sharing. As long as the message is a primitive type and is passed by value (or an immutable type passed by reference), there’s no problem. But when you pass a mutable Object as a message, in reality you are passing a reference (a handle) to it. Suddenly your message is shared and may be accessed by more than one thread at a time. You either need additional synchronization outside of the Actor model or risk data races. Languages that are not strictly functional, including Scala, have to deal with this problem. They usually pass this responsibility, conveniently, to the programmer.
-Kilim
Java is not a good language to implement the Actor model. You can extend Java though, and there is one such extension worth mentioning called Kilim by Sriram Srinivasan and Alan Mycroft from Cambridge, UK. Messages in Kilim are restricted to objects with no internal aliasing, which have move semantics. The pre-processor (weaver) checks the structure of messages and generates appropriate Java code for passing them around. I tried to figure out how Kilim deals with waiting on multiple mailboxes, but there isn’t enough documentation available on the Web. The authors mention using the select statement, but never provide any details or examples.
Correction: Sriram was kind enough to provide an example of the use of select:
int n = Mailbox.select(mb0, mb1, .., timeout);
The return value is the index of the mailbox, or -1 for the timeout. Composability is an important feature of the message passing model.
Dynamic Networks
Everything I described so far is common to CSP (Communicating Sequential Processes) and the Actor model. Here’s what makes actors more general:
Connections between actors are dynamic. Unlike processes in CSP, actors may establish communication channels dynamically. They may pass messages containing references to actors (or mailboxes). They can then send messages to those actors. Here’s a Scala example:
receive { case (name: String, actor: Actor) => actor ! lookup(name) }
The original message is a tuple combining a string and an actor object. The receiver sends the result of lookup(name) to the actor it has just learned about. Thus a new communication channel between the receiver and the unknown actor can be established at runtime. (In Kilim the same is possible by passing mailboxes via messages.)
Actors in D
The D programming language with my proposed race-free type system could dramatically improve the safety of message passing. Race-free type system distinguishes between various types of sharing and enforces synchronization when necessary. For instance, since an Actor would be shared between threads, it would have to be declared shared. All objects inside a shared actor, including the mailbox, would automatically inherit the shared property. A shared message queue inside the mailbox could only store value types, unique types with move semantics, or reference types that are either immutable or are monitors (provide their own synchronization). These are exactly the types of messages that may be safely passed between actors. Notice that this is more than is allowed in Erlang (value types only) or Kilim (unique types only), but doesn’t include “dangerous” types that even Scala accepts (not to mention Java or C++).
I will discuss message queues in the next installment.
July 16, 2009 at 12:18 pm
Bartosz, thank you for the interesting as always blog.
There is important possibility for agents in C++ – inversion of control + full asynchronism. Such approach hides uses only one queue but hides all dynamic casting.
Consider:
The syntactic overhead is minimal to a certain extent. There are definitely apologetics admirers of direct control flow. However I think it’s more a matter of a personal taste.
IMHO inverted control flow has some orderliness and allows to express agent state more directly.
Such approach allows incorporation of some pieces of join-calculi as well:
Or even:
I’m also thinking about incorporation of immutable, unique and explicitly shared messages. Well, definitely if user will want to violate these properties he will succeed in C++.
This can’t provides the same level of user experience as new language, however the fact that it works on top of C++ quite appealing.
Will be interesting to hear you comments on this approach.
—
Dmitriy V’jukov
July 16, 2009 at 12:20 pm
F#$% wordpress has eaten all templates in the code.
Is here some possibility to post angle brackets?
July 16, 2009 at 12:23 pm
Ok, here it is:
http://pastebin.com/f6f4aca91
July 16, 2009 at 4:20 pm
I took the liberty of editing your comment. The trick is to put code between <pre> and </pre> tags and convert all < to <.
July 16, 2009 at 8:28 pm
Thank you for another interesting blog. I too think there’s a great synergy between message passing and a concurrency aware type system, so I’m looking forward to your next post. However, when I think of Actors I think of concurrent objects. This leads me to think less of message passing and more of (local or remote) procedure calls. This eliminates the message typing problem, as the types are encoded in the function call id. It also leads me away from threads and towards task based systems (like threading building blocks or Cilk). I like tasks as they can 1) provide a common backend for parallelized pure functions, futures/Cilk, actors and OpenMP/OpenCL styles of programming and 2) are very lightweight. Point #1 provides composability and interoperability and point #2 encourages the use of N-fold algorithms. Threads, although more flexible, are very heavy weight, which encourages the use of k-fold algorithms, which cause major performance issues whenever k != your number of cores.
July 17, 2009 at 3:27 am
Great post! Most interesting blog I’ve read about programming (maybe because I’m interested in exactly the same topics!). Are these additions you call “proposed race-free type system” going to be included in the D2?
July 17, 2009 at 11:17 am
Dmitriy: How is your approach different from the C++ example I gave (other than grouping handlers in one class)?
July 17, 2009 at 11:28 am
Robert: Remote Procedure Call (RPC) is less flexible than the Actor model and many people think RPC is the wrong abstraction. Behind the scenes, RPC sends a message and blocks until the response arrives. Actors don’t block on send. RPC call must always protect itself from broken communications–the receiver dying or the connection going down. That breaks the abstraction of RPC call behaving just like a function call.
As far as threads vs. tasks: the Actor model can be easily built on tasks, and that’s what Scala does. Maybe I should write a separate post about this?
July 17, 2009 at 11:29 am
Hmmm… practically they are basically the same. However I do not agree with formal description.
I would not say that it’s ‘casting’. Even implementation may not contain any casts, not saying that for user it looks more like pattern matching (pattern given by a message type).
I would not say syntax is awkward. This is how in main part I would like to see agents in any [new] language. I.e. all message types that agent accept are explicitly described in the interface and not hidden somewhere in the methods.
July 17, 2009 at 11:30 am
Btw, why exactly you call that awkward?
July 17, 2009 at 11:38 am
Álvaro: To be completely frank: There’s little chance of me single-handedly convincing other D designers to accommodate my proposal. They think it’s too complex for the average programmer. Of course, average programmers are the ones most likely to fall for the traps of concurrent programming. My system would at least tell them where the error is.
July 17, 2009 at 11:42 am
We have a paper in PPPJ this year, which discusses standard Actor semantics (including defining encapsulation for actors, fairness etc) and provides a comparison of various implementations of Actors on JVM for semantics, abstractions and performance.
Click to access paper.pdf
Our belief is that messaging should have one semantics i.e by-value only. Many different variants of messages leads to confusion and complicated analysis, reasoning and testing.
July 17, 2009 at 12:16 pm
I think a post on how Scala does actors and building actors on top of tasks would be interesting.
Regarding RPC, I agree that synchronous RPC is definitely the wrong model. However, just as message passing can be synchronous or asynchronous, RPC can be synchronous or asynchronous (Futures for example, follow an asynchronous RPC model (where remote is defined as being a different thread/fiber, instead of a different PC)). Void functions therefore become simple, asynchronous messages (with the same associated performance).
Also, beyond trivial examples, I’d expect the receive function to look like a manual v-table. i.e.
P.S. Thanks for the paper link Rajesh.
July 18, 2009 at 3:23 am
Bartosz: Well, I won’t get into the political side of how D is being designed, but I came to D2 specially looking for a way using some concepts that I discovered from Erlang and the Java extension Kilim. Maybe the programmer who is not interested in this things can stick to D1, as many people do (those who want just the C++ “done right”). Anyway, what are the chances to introduce this? DMD D2 is being driven by Andrei and Walter, which is ok to get decisions made in a faster pace than the committee way a la C++0x. But could this be introduced as a compiler extension, library or any other way? (I know this is thought as a language feature) Most probably in future compilers like DIL or LDC2? Eventually, they will take the lead…
July 20, 2009 at 2:59 pm
To Álvaro: We’ve just had a conference–Andrei, Walter and I. We decided that we have a deadline for D2, so we have to cut some features and postpone them to D3. The race-free type system won’t make it–there just isn’t enough time to implement and test it. There will be however a stopgap solution for message passing in Phobos. It will be limited but safe.
July 20, 2009 at 5:33 pm
To Rajesh: Very interesting paper. I’m wondering though if location transparency (not to mention mobility) is worth the sacrifices in performance. You do want your local message passing to be lightning fast, which means passing by value is simply not sustainable for anything but basic data types. My approach is to combine message passing with a race-free type system a la Boyapati and Rinard ( http://www.eecs.umich.edu/~bchandra/publications/oopsla01.pdf ). Besides being able to use values and immutable types for messages, it allows safe passing of unique types and monitors.
July 20, 2009 at 5:41 pm
To Robert: Asynchronous RPC seems to me a contradiction in terms. Futures are not considered RPC either. If you overgeneralize RPC that way, it simply becomes message passing.
July 21, 2009 at 12:06 am
Thanks Bartosz. Location transparency and Mobility do seem like a luxury now, but as multicores scale, it will be quite expensive to provide a shared memory view. And load-balancing will require moving actors around. Then, these may become a necessity. We have another paper in progress which analyzes and improves the cost of providing naming and mobility.
Ownership types are quite promising. I ll look forward to hopefully a simple and intuitive approach in integrating them with messages. When Kilim tried to introduce linear types for uniqueness, it turned out to be quite complex. Btw, I only meant by-value by semantics; the implementation is free to optimize 🙂
July 21, 2009 at 5:22 pm
“Location transparency and Mobility do seem like a luxury now, but as multicores scale, it will be quite expensive to provide a shared memory view.”
I’ve heard this said before, but I’m not sure what the alternatives are. Do you think the next generation of multiprocessors will have NUMA architecture and there will be a primitive in the language to send message from one processor to another? I can’t even imagine a high level programming model for that.
“And load-balancing will require moving actors around.”
I understand that once you make use of location transparency and distribute your application, you might want mobility. However, within a single process “load balancing” is done by the scheduler, which connects (UMA or NUMA) processors to threads (be it the system scheduler or a user scheduler). That’s the way Scala went and so is the new Microsoft C++ runtime.
Judging by the current state of affairs, distributed applications are rare and far between, so I’m not sure if implementing location transparency at the cost of local performance pays off. You make a few distributed-systems developers happy, but make the majority of multiprocessor applications slower.
July 21, 2009 at 9:30 pm
Tilera, a processor company, has the largest general purpose processor with 64 core, and in their API they support message-passing between cores.
In fact do not need a distributed shared memory processor; even on a shared memory processor there are contention and bad cache effects for a single-process-multiple-threads model. Check this out (specially page 8):
Click to access euc_smp.pdf
Scala has no other option since it runs on JVM, and there is no portable support for pinning a thread to a core on JVM.
July 23, 2009 at 5:52 am
“Distributed applications are few and far between”? Can I have some of what you’re smoking, please? It must be some *really* good stuff. This site runs on top of a distributed application (wordpress.com) and most visitors probably found this through another (Google).
Did you mean to say that most developers don’t work on distributed applications? That might almost be defensible *today* but will become less and less true in the very near future. The economies of connecting together two- or four-socket systems with commodity interconnects vs. building bigger shared-memory systems are just too compelling, and the better fault isolation in the model doesn’t hurt either. Practically all scientific computing has been not only multi-core but multi-node for years, as has most serious data mining. The domain for which that’s true will only expand over time. Maybe we won’t see too much of that on the desktop this year (except for GPGPUs which are more of a disjoint-memory model) but concurrency models that only work in a shared-memory environment will be almost irrelevant ten years from now.
July 23, 2009 at 2:18 pm
To Jeff: It’s hard to come up with statistics that would support either view. In my experience distributed programming is used in writing systems not apps. For instance, Google uses the MapReduce system and GFS (Google File System). When you write an app for MapReduce, you use the special API. This API hides message passing and process migration from the end programmer.
The whole internet is a distributed system, but when you write apps for it, you use JavaScript or C# on top of the browser API and DOM. Even the browser is not distributed–you run it locally.
I don’t know what WordPress servers are using internally, but my guess would be some kind of distributed file system.
July 27, 2009 at 7:07 pm
You mention “The authors mention using the select statement, but never provide any details or examples”.
The docs and the examples are part of the distribution. This is how you receive from multiple mailboxes of different types:
int n = Mailbox.select(mb0, mb1, .., timeout)
This yields the nth mailbox that is receive-ready or -1 on timeout.
In other words, this separates the get-ready event from the actual action of doing the get.
July 27, 2009 at 9:03 pm
Thanks, Sriram. That’s what I needed to know. I’ll edit the post to include this information.
July 28, 2009 at 8:59 pm
Your type system seem to cover everything i know about multithreading.
But how would you integrate the resource allocation algorithm for the SCOOP run-time system. see Figure 9 of http://www.jot.fm/issues/issue_2002_08/article8/
July 28, 2009 at 9:20 pm
This mechanism’s give us the ability to reserve several resources through a single call having several separate arguments.
This allow us to solve the dinning philosopher probleme naively without deadlock.
July 29, 2009 at 1:34 pm
The ownership system does not address deadlock prevention. From what I gleamed from the Eiffel paper, SCOOP must address it, because it attempts to lock multiple objects, implicitly, in one transaction. It’s a much higher-level system than what I’m proposing. It’s an interesting idea, although it may be costly at runtime.
August 12, 2009 at 2:54 am
I don’t know if you are aware of this but there is a paper by Philipp Haller and Martin Odersky that talks about unique object references which could enhance Scala’s isolation property. http://lamp.epfl.ch/~phaller/uniquerefs/
August 12, 2009 at 9:11 am
Thanks for the pointer. Odersky always has some unique insights. I’ll study the paper.
August 13, 2009 at 6:34 pm
Saw your talk at Sea Jug, good stuff, thanks.
In response to this article:
Lately I have been toying with this library:
http://www.cunei.com/polyd/
which allows one to multiple dispatch via annotations, which completely hides the test and cast problem with Java.
August 14, 2009 at 1:57 pm
Thanks for the information, Jeremy. I had a look at PolyD but I don’t see how it can be applied to message passing. The difference is that in message passing you must be able to store different types of messages in the mailbox. The receiver thread picks up those messages and dispatches them to different handlers. But while in the queue, the message’s type is erased. So you can’t dispatch based on type, which is what PolyD does. At least that’s my understanding.