I've been told that (Scala) Actors never actually perform two operations at the same time, which suggests that the act (or react? or receive?) method is inherently synchronized. I know a long operation in an act method can cause blocking issues, and I assume that access to the message queue must be synchronized in some way... but...
What was suggested is that an actor receiving messages telling it to increment an internal counter would increment the counter in a threadsafe way. That no two update messages would be processed simultaneously, and so no two messages could attempt to update the counter at the same time.
A counter attribute in an actor sounds like "shared state."
Is it really true that such an operation would be completely threadsafe? If so, how does an actor make use of multiple core machines in some efficient way? How is an actor multi threaded at all?
If not, what's an appropriate idiomatic way to count messages in a threadsafe way without needing some synchronized/volatile variable?
The Actor model can be used to isolate mutable state from the outside world. When you have a mutable state (for example a global registry of IDs assigned to multiple concurrent processes), you can wrap that mutable state up inside an Actor and make the clients communicate with the Actor via message-passing. That way only the actor accesses the mutable state directly, and as you say, the client messages queue up to be read and processed one-by-one. It is important for messages to be immutable.
To avoid the queue getting full, it is important that the message processing (react
, receive
, etc.) be as short as possible. Long-running tasks should be handed off to an other actor:
1. Actor A receives a message M from sender S
2. A spawns a new actor C
3. A sends (S, f(M)) to C
4. In parallel:
4a. A starts processing the next message.
4b. C does the long-running or dangerous (IO) task,
When finished, sends the result to S,
and C terminates.
Some alternatives in the process:
(S, result)
back to A who forwards to SActorRef C => (Sender S, Message M)
so in case it sees C fail, it can retry processing M with a new Actor.So to recap, an Actor is multi-threaded to the extent that multiple clients can send it multiple messages from various threads, and it is guaranteed that the Actor will process all these messages serially (although the ordering can be subject to various non-overly strict constraints).
Note that while the Actor's react
code may be executed on various threads, in a single given point of time it is executed on a single given thread only (you can picture this that the Actor jumps from thread to thread as the scheduler sees fit, but this is a technical detail). Note: The internal state still doesn't need syncronization, since Actors guarantee happens-before semantics between processing messages.
Parallelism is achieved by having multiple Actors working in parallel, usually forming supervisor hierarchies or balancing workload.
Note that if all you need is concurrent/asynchronous computations, but you don't have or can get rid of global state, Future
s are a better composing and easier concept.
"An actor" is not multithreaded, but an actor system usually is. Each actor executes only one action at a time, but when there are multiple actors, each can operate on its respective encapsulated state in parallel. A counter attribute is not shared mutable state if it is not shared between actors.
If your question is about the implementation of the actor system, that varies and is usually configurable, i.e. default Scala actors can be configured to run either single threaded or on a thread pool or using Java ForkJoin tasks. I find the scala.actors source very readable, so I recommend having a look if you want to understand what's going on.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With