- Issues with shared mutable state
- Actor model
- Actor system
- Define actor
- Message processing
- Side effects
- Non-typed
- Asynchronous and non-blocking
- Create an actor
- Send Message
- Message Answering
- Inquiry ask Mechanism
- A stateful actor.
- Summary
Note: The original title of this article: "The neophyte's Guide to Scala part 14:the Actor approach to Concurrency", Daniel westheide, the original link: Http://danie Lwestheide.com/blog/2013/02/27/the-neophytes-guide-to-scala-part-14-the-actor-approach-to-concurrency.html, This article is the 14th in a series of articles by the author of the Scala Beginner's Guide, an introductory introduction to Scala's actor programming model and Akka, translated into Chinese on my technical blog after obtaining authorization from the author. This article source: http://blog.csdn.net/bluishglc/article/details/53155454 prohibited any form of reprint, or will entrust CSDN official maintenance rights!
After a few previous articles on how to dramatically increase flexibility and compile-time security with the Scala type system, we are now back to one of the topics discussed earlier in this series: how Scala handles concurrency.
We've described the scenario of combining Future
asynchronous processing in a way that's good for many problems, but it's not the only scenario that Scala offers, and the other cornerstone of Scala's processing concurrency is the "Actor model", which provides a concurrency scenario based on inter-process message passing.
Actor is not a new concept, the most well-known implementation is on Erlang, the Scala core library has its own actor in the early implementation, But after the Scala2.11 version, it faced an abandoned fate, because it would be replaced by the actor implementation provided by Akka, Akka as the Scala de facto actor standard for a long time.
In this article you will learn about the concept of the Akka Actor model and learn how to use the Akka Toolbox for basic programming, and we do not intend to discuss all the contents of the Akka actor in depth, so, unlike most of the articles in front of this series, this article is designed to give you an idea of Akka, Inspire your interest in it.
Issues with shared mutable state
The current mainstream concurrency solution is a "shared mutable State" (Gkfx mutable, a large number of stateful objects whose state can be changed in many parts of the application, with each modification being made on its own thread. In such scenarios, code often spreads over read/write locks, preventing multiple threads from modifying at the same time to ensure that the change in object state is controllable. At the same time, we should try to avoid locking too large blocks of code, because this will greatly weaken the performance of the program.
Actor model
The widely used "shared mutable State" scenario requires that you always keep in mind that your code is running in a concurrent scenario, and you have to design and write your app from start to finish in a concurrent way, and you'll find it hard to add support later. The actor programming model is dedicated to avoiding all of the above issues, and it allows you to write readable high-performance concurrency code.
The idea of the Actor model is to think of your application as being made up of a number of lightweight entities called "actors" that each actor is responsible for only a small task, a single, clear, complex business logic that can be accomplished through multiple actors collaborating with each other. For example, delegating tasks to other actors or passing messages to collaborators.
Actor system
Actors are pathetic creatures that cannot live on their own. Each actor in the Akka is created and maintained by an actor system. An actor system provides a full set of accessibility features, but now we don't have to worry about that.
Let's start with the sample code and we need to add the following resolver and dependency in the Scala 2.10-based SBT project:
"Typesafe Releases""http://repo.typesafe.com/typesafe/releases""com.typesafe.akka"%% "akka-actor" %"2.2.3"
Now, let's create a actorsystem, which will be the hosting environment for all user-defined actors.
import akka.actor.ActorSystemobject Barista extends App { val system = ActorSystem("Barista") system.shutdown()}
We created a Actorsystem instance, named it "Barista" (the barista), and we'll explain it in the "buy and make coffee" scenario, which we mentioned in an earlier article in this series, composable futures.
At the end of the program, as a good citizen, when we no longer use this actorsystem, we have to remember to close it.
Define actor
Your application is composed of dozens of or millions of actors depending on your use case, but Akka is fully capable of handling millions number of actors, you might think that creating so many actors must be insane, but in fact, theactor and the thread are not one by one corresponding relationships , this is very important, otherwise the system's memory will be consumed very quickly. Due to the non-blocking nature of the actor, a thread can serve different actors, Akka (translator Note: Specifically, dispatcher) will allow the thread to switch between actors, depending on who has the message to be processed to assign the thread to it.
To understand what actually happened, let's start by creating a simple actor: Barista
It's responsible for receiving coffee order making coffee, and we simply let it print a message that says it's done with the coffee order:
Sealed trait coffeerequest Case Object cappuccinorequest extends coffeerequest Case Object espressorequest extends coffeerequest ImportAkka.actor.Actor class Barista extends Actor { defReceive = { CaseCappuccinorequest = println ("I had to prepare a cappuccino!") CaseEspressorequest = println ("Let's prepare an espresso.") }}
First, we want to define the type of message that the actor can understand. If the message has various parameters, we typically use case class to encapsulate the message and pass it between actors. If the message has no parameters, simply use Case object, as we wrote here.
In any case, make sure your message is immutable, or there will be bad consequences.
Next, let's look at the Barista
class, which is a concrete class, inherited from the Actor
trait, which defines a method receive
that returns Receive
the value of a type. Receive
is PartialFunction[Any, Unit]
an alias for the type.
Message processing
receive
What is the meaning of the method? Its return type PartialFunction[Any, Unit]
may make you feel weird.
Simply put, receive
this partial function returned by the method represents the actor's handling logic for all messages passed to it. Whenever the other part of your system (another actor or something) sends a message to the actor, Akka will eventually process the message by invoking the partial function returned by the actor's receive
method. This message is passed as a parameter to the partial function when it is called.
Side effects
When dealing with messages, you can have the actor do whatever you want, but it just can't have a return value !
Why?
Since the partial function qualifies the return type only Unit
, we have emphasized that in functional programming you should always try to use pure functions as much as possible, so you may be surprised by this. For a concurrent programming model, this is actually reasonable, the actor receives each message is the isolation processing, is one piece carries on, does not need the synchronization or the lock mechanism. Where side effects can occur, they are handled in a controlled manner.
Non-typed
Another side effect of the partial function is the type of argument it expects, that is, the message is untyped Any
, which seems confusing in Scala, which has such a powerful type of system.
Untyped mates Some important design decisions allow us to do a lot of things, such as forwarding messages to other actors, load balancing, or acting actors to prevent senders from knowing too much detail.
In practice, it is usually not a problem to have a parameter that represents a message in a partial function, and if you are using a strongly typed message, you can use pattern matching to process the corresponding type of message, as we did in our previous code.
But sometimes, a weak type of actor can actually cause a bad bug. If you are accustomed to and highly dependent on a strongly typed system, you can look at the characteristics of Akka in the experimental phase: Typed Channels.
Asynchronous and non-blocking
I mentioned earlier that Akka "eventually" will let your actor handle the messages sent to it. The message's sending and processing is an asynchronous, non-blocking process. The sender will not be blocked until the receiver finishes processing the message, it will continue its work directly, and the sender may expect to get a feedback message from the processor, but perhaps it does not care about the results of the message at all.
When some components send a message to an actor, what really happens is that the message is delivered to the actor Mailbox
, and we can basically think of mailbox as a pair of columns. The process of putting a message into an actor's mailbox is also non-blocking, for example: the sender does not wait for the message to enter the receiver's mailbox column.
Dispatcher
The actor will be notified that its mailbox has a new message, and if the actor is processing the message at hand, it Dispatcher
will select an available thread from the current execution context, once the actor has finished processing the previous message, It will let the actor take the message out of the mailbox on the prepared thread.
The actor blocks the thread assigned to it until it starts processing a message, but it does not block the sender of the message, which means that a lengthy operation can affect overall performance. Because all the other actors had to be scheduled to pick one from the rest of the threads to process the message (translator Note: Increased overhead for thread scheduling).
Therefore, your receive bias function follows a core guideline: to shorten its execution time as much as possible (translator Note: You can split the task into smaller units and delegate more granular actors to execute). More importantly, try to avoid calling code that will cause blocking in your message handling code.
Of course, there are some things you can't completely avoid, for example, today's mainstream database drivers are basically blocking, and if you want your program to persist or query data based on the actor, you'll be faced with this problem. Now there are some solutions to these problems, but as a partial introductory article, this article does not involve first.
Create an actor
After defining the actor, how do we actually use our Barista
? To do this, I need Barista
an instance for the creation. You might use the following general practice of instantiating an actor by calling the constructor:
valnew// will throw exception
No way! Akka will return you with an ActorInitializationException
exception. The thing is this: in order for the actor to work well, your actor must be entrusted ActorSystem
with its components. Therefore, you need to use Actorsystem to help you create this instance:
import akka.actor.{ActorRef, Props}val"Barista")
Defined on Acotorsysytem The actorOf
method expects an Props
instance, and the configuration information for the newly created actor is encapsulated in this instance, and the Props
method gives the actor a name. We are using the simplest way to create an props instance, that is, by invoking the Apply method of the associated object of props, and specifying its type parameters. Akka will then invoke its constructor based on the given type to create a new actor instance.
Note: actorOf
The object type returned is not Barista
but ActorRef
(translator note: Akka's hosting of the actor manifests itself in many places, as mentioned earlier, you cannot create an actor instance directly on one hand, here, after creation, you get not the actor instance itself but a ActorRef
). The actor never communicates directly with other actors, so it is not necessary to get a direct reference to an actor instance, but rather to have actors or other components get the actors that need to send messages ActorRef
.
So, ActorRef
playing the role of the actor instance agent, there is a lot of benefit, because one ActorRef
can be serialized, we can let it proxy an actor on a remote machine, and the actor in the back is a ActorRef
local JVM or a remote machine, This is transparent to the user, and we call this feature "location transparency".
Keep in mind that ActorRef
not type-parameterized, anything ActorRef
can be exchanged so that you can send arbitrary messages to arbitrary ActorRef
. This design, as we mentioned earlier, allows you to simply modify the network topology of your actor system without having to make any changes to the sender.
Send Message
Now we have created an actor instance of barista and obtained a reference to it ActorRef
. We can now send a message to it. This is achieved by invoking ActorRef
the !
method:
barista ! CappuccinoRequestbarista ! EspressoRequestprintln("I ordered a cappuccino and an espresso")
The calling !
method is a "no matter after call" operation: you tell barista you want a cappuccino, but you won't be waiting for it to respond. This is the most common way of interacting with actors in Akka. By calling !
, you tell Akka to add your message to the recipient's mailbox queue, as mentioned earlier, this is non-blocking, and the actor acting as the receiver will eventually process your message.
Because of the asynchronous nature, the execution of the above code is indeterminate, and it may be a result:
toaaandan espressoLet‘s prepare an espresso.
Even if we were to send two messages to barista's mailbox in succession, we would describe the customer finishing the coffee information before printing it to the console before espresso was finished.
Message Answering
Sometimes, you might want to send a reply message to a message sender. In order for you to do that, the actor has a sender
method called, which returns the sender of the last message ActorRef
.
But how does it know who the sender is? The answer can be seen from the !
function signature of the method, and its second argument is an implicit parameter:
def !(message: Any)(implicit sender: ActorRef = Actor.noSender): Unit
When invoking the method of an actor !
, the actor is ActorRef
passed in as a hermit parameter sender.
Let's change Barista
it so that it sends a billing message Bill
to the sender before it works CoffeeRequest
:
Case class Bill(cents:int) Case Object closingtime class Barista extends Actor { defReceive = { CaseCappuccinorequest = sender! Bill ( -) println ("I had to prepare a cappuccino!") CaseEspressorequest = sender! Bill ( $) println ("Let's prepare an espresso.") CaseClosingtime = Context.system.shutdown ()}}
In the above code we have introduced a new message ClosingTime
, Barista
the way to deal with this message is to close the Actorsystem, all actors can be obtained from the ActorContext
Actorsystem.
Now, let's introduce the second actor, which represents the "customer".
case object caffeinewithdrawalwarning class customer (caffeinesource:actorref) extends Span class= "Hljs-title" >actor { def receive = {case caffeinewithdrawalwarning = caffeinesource! Espressorequest case Bill (cents) = println (S "I have To pay $cents cents, or else! ")}}
This is a coffee-addicted customer, and once the caffeine intake is reduced, he will have to buy coffee to drink. We give Customer
the constructor a pass, that is ActorRef
caffeineSource
, Customer
don't know this ActorRef
point to one Barista
, but it knows it can send a CoffeeRequest
message to it, that's all.
Finally, let's instantiate the two actors and Customer
send a CaffeineWithdrawalWarning
message (Translator Note: This message means: Caffeine intake is reduced, a warning is given, the implication is that the customer's body tells the customer: "You should drink coffee!" "), let the program run up:
val"Barista")val"Customer")customer ! CaffeineWithdrawalWarningbarista ! ClosingTime
Here, for this Customer
, we used a different factory method to create an Props
instance: the type of the actor we passed in and the parameters required by its constructor. We do this because we want to pass it to Barista
the ActorRef
Customer
constructor.
Send a CaffeineWithdrawalWarning
message to a caffeine-addicted customer, the customer responds by buying a cup of espresso immediately, sending a EspressoRequest
message to the barista, which sends a billing message to the customer. The corresponding output might be this:
Let‘s prepare an espresso.to200orelse!
First, when the EspressoRequest
message is processed, Barista
a message is sent to sender, that is Customer
, however, the operation does not block the operation behind it. Barista
you can continue processing EspressoRequest
as if it were printed as a console. Soon, the Customer
message is processed and Bill
then printed to the console.
Inquiry (ASK) mechanism
Sometimes, sending a message to an actor and expecting to return a response message this pattern does not apply to certain scenarios, the most common example being that when some components are not actors but need to interact with the actor, they cannot receive messages from the actor.
In this case, Akka has a Ask
mechanism that provides a bridge between actor-based concurrency and future-based concurrency, which works from the client's point of view:
import akka.pattern.askimport akka.util.Timeoutimportval timeout = Timeout(2.val ec = system.dispatcherval f: Future[Any] = barista2 ? CappuccinoRequestf.onSuccess { case Bill(cents) => println(s"Will pay $cents cents for a cappuccino")}
First, you need to introduce support for the ASK syntax (that is import akka.pattern.ask
), and ?
Future
create an implicit variable, timeout, for the method to return Future
ExecutionContext
. Here we simply use the Actorsystem default dispatcher, which is also one ExecutionContext
.
As you can see, the return of the future is untyped and it is one Future[Any]
. It is not surprising that since it receives the message from an actor, these actors (ACTORREF) are not yet typed, and how can the returned future be typed?
For the actor being questioned, this is no different from sending a message to the sender of the message being processed, so we Barista
Barista
don't need to make any changes to it when we use the ASK mechanism to get a reply from it.
Once the actor being questioned sends a feedback message to the sender of the message, the return is Future
Promise
completed.
On the whole, unsolicited notification is better than asking, because it consumes less resources and Akka is not intended for "polite" people. However, there are some scenarios where you can only use the inquiry method, but that is nothing, Akka can also workplace very well.
A stateful actor.
Each actor can maintain an internal state, but it is not required. Sometimes, a large part of the system's overall state is made up of information that is carried by immutable messages passed between actors.
An actor processes only one message at a time, which in the process may modify its internal state, which means that the actor's internal state is mutable, but since each message is separated from each other, the internal state of the actor is not messed up by concurrency.
To illustrate this point, let's change the stateless state to a Barista
stateful one, and we'll add an order counter to it:
class Barista extends Actor { varCappuccinocount =0 varEspressocount =0 defReceive = { CaseCappuccinorequest = sender! Bill ( -) Cappuccinocount + =1println (S"I had to prepare Cappuccino # $cappuccinoCount") CaseEspressorequest = sender! Bill ( $) Espressocount + =1println (S"Let ' s prepare Espresso # $espressoCount.") CaseClosingtime = Context.system.shutdown ()}}
We introduced two variables, cappuccinoCount
and expressoCount
the number of orders representing the two types of coffee respectively. This is the first time we have used this article in this series var
, although we should try to avoid it in functional programming var
, but this is the only way for your actor to carry the "state". Since each message is handled in isolation, the code above is the same as the effect used in a non-actor environment AtomicInteger
.
Summary
Here, our introduction to using the Actor model for concurrent programming and how to use this programming paradigm in Akka is coming to an end. We just introduced briefly, ignoring some of the important concepts in Akka, and I hope this will allow you to have a preliminary understanding of this concurrent programming approach and stimulate your interest in learning.
In the next article, I'll reinforce our example by adding some meaningful behavior to it to introduce more information about the Akka actor, such as how to handle errors in an actor system.
Actor-based concurrency scheme