clarify need to carefully managing blocking, see #2616

This commit is contained in:
Roland 2012-10-15 21:34:31 +02:00
parent ef80428b02
commit c27389ca1b
7 changed files with 86 additions and 10 deletions

View file

@ -89,10 +89,9 @@ Actor Best Practices
bothering everyone else needlessly and avoid hogging resources. Translated
to programming this means to process events and generate responses (or more
requests) in an event-driven manner. Actors should not block (i.e. passively
wait while occupying a Thread) on some external entity, which might be a
lock, a network socket, etc. The blocking operations should be done in some
special-cased thread which sends messages to the actors which shall act on
them.
wait while occupying a Thread) on some external entity—which might be a
lock, a network socket, etc.—unless it is unavoidable; in the latter case
see below.
#. Do not pass mutable objects between actors. In order to ensure that, prefer
immutable messages. If the encapsulation of actors is broken by exposing
@ -112,6 +111,53 @@ Actor Best Practices
performance) and it also reduces the number of blocking calls made, since
the creation of top-level actors involves synchronous messaging.
Blocking Needs Careful Management
---------------------------------
In some cases it is unavoidable to do blocking operations, i.e. to put a thread
to sleep for an indeterminate time, waiting for an external event to occur.
Examples are legacy RDBMS drivers or messaging APIs, and the underlying reason
in typically that (network) I/O occurs under the covers. When facing this, you
may be tempted to just wrap the blocking call inside a :class:`Future` and work
with that instead, but this strategy is too simple: you are quite likely to
find bottle-necks or run out of memory or threads when the application runs
under increased load.
The non-exhaustive list of adequate solutions to the “blocking problem”
includes the following suggestions:
- Do the blocking call within an actor (or a set of actors managed by a router
[:ref:`Java <routing-java>`, :ref:`Scala <routing-scala>`]), making sure to
configure a thread pool which is either dedicated for this purpose or
sufficiently sized.
- Do the blocking call within a :class:`Future`, ensuring an upper bound on
the number of such calls at any point in time (submitting an unbounded
number of tasks of this nature will exhaust your memory or thread limits).
- Do the blocking call within a :class:`Future`, providing a thread pool with
an upper limit on the number of threads which is appropriate for the
hardware on which the application runs.
- Dedicate a single thread to manage a set of blocking resources (e.g. a NIO
selector driving multiple channels) and dispatch events as they occur as
actor messages.
The first possibility is especially well-suited for resources which are
single-threaded in nature, like database handles which traditionally can only
execute one outstanding query at a time and use internal synchronization to
ensure this. A common pattern is to create a router for N actors, each of which
wraps a single DB connection and handles queries as sent to the router. The
number N must then be tuned for maximum throughput, which will vary depending
on which DBMS is deployed on what hardware.
.. note::
Configuring thread pools is a task best delegated to Akka, simply configure
in the ``application.conf`` and instantiate through an :class:`ActorSystem`
[:ref:`Java <dispatcher-lookup-java>`, :ref:`Scala
<dispatcher-lookup-scala>`]
What you should not concern yourself with
-----------------------------------------

View file

@ -5,10 +5,6 @@ package docs.dispatcher;
//#imports
import akka.actor.*;
import akka.actor.ActorRef;
import akka.actor.Props;
import akka.actor.UntypedActor;
import akka.actor.UntypedActorFactory;
//#imports
//#imports-prio
@ -37,6 +33,7 @@ import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import scala.Option;
import scala.concurrent.ExecutionContext;
import com.typesafe.config.ConfigFactory;
@ -75,6 +72,14 @@ public class DispatcherDocTestBase {
.withDispatcher("my-pinned-dispatcher"));
//#defining-pinned-dispatcher
}
public void compileLookup() {
//#lookup
// this is scala.concurrent.ExecutionContext
// for use with Futures, Scheduler, etc.
final ExecutionContext ex = system.dispatchers().lookup("my-dispatcher");
//#lookup
}
@Test
public void priorityDispatcher() throws Exception {

View file

@ -13,6 +13,15 @@ Default dispatcher
Every ``ActorSystem`` will have a default dispatcher that will be used in case nothing else is configured for an ``Actor``.
The default dispatcher can be configured, and is by default a ``Dispatcher`` with a "fork-join-executor", which gives excellent performance in most cases.
.. _dispatcher-lookup-java:
Looking up a Dispatcher
-----------------------
Dispatchers implement the :class:`ExecutionContext` interface and can thus be used to run :class:`Future` invocations etc.
.. includecode:: code/docs/dispatcher/DispatcherDocTestBase.java#lookup
Setting the dispatcher for an Actor
-----------------------------------

View file

@ -186,6 +186,13 @@ class DispatcherDocSpec extends AkkaSpec(DispatcherDocSpec.config) {
//#defining-pinned-dispatcher
}
"looking up a dispatcher" in {
//#lookup
// for use with Futures, Scheduler, etc.
implicit val executionContext = system.dispatchers.lookup("my-dispatcher")
//#lookup
}
"defining priority dispatcher" in {
//#prio-dispatcher

View file

@ -13,6 +13,15 @@ Default dispatcher
Every ``ActorSystem`` will have a default dispatcher that will be used in case nothing else is configured for an ``Actor``.
The default dispatcher can be configured, and is by default a ``Dispatcher`` with a "fork-join-executor", which gives excellent performance in most cases.
.. _dispatcher-lookup-scala:
Looking up a Dispatcher
-----------------------
Dispatchers implement the :class:`ExecutionContext` interface and can thus be used to run :class:`Future` invocations etc.
.. includecode:: code/docs/dispatcher/DispatcherDocSpec.scala#lookup
Setting the dispatcher for an Actor
-----------------------------------

View file

@ -40,7 +40,7 @@ class CreationActor extends Actor {
case result: MathResult result match {
case MultiplicationResult(n1, n2, r)
println("Mul result: %d * %d = %d".format(n1, n2, r))
case DivisionResult(n1, n2, r)
case DivisionResult(n1, n2, r)
println("Div result: %.0f / %d = %.2f".format(n1, n2, r))
}
}

View file

@ -40,7 +40,7 @@ class LookupActor extends Actor {
def receive = {
case (actor: ActorRef, op: MathOp) actor ! op
case result: MathResult result match {
case AddResult(n1, n2, r)
case AddResult(n1, n2, r)
println("Add result: %d + %d = %d".format(n1, n2, r))
case SubtractResult(n1, n2, r)
println("Sub result: %d - %d = %d".format(n1, n2, r))