Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Programming_in_Scala,_2nd_edition.pdf
Скачиваний:
25
Добавлен:
24.03.2015
Размер:
22.09 Mб
Скачать

Section 32.3

Chapter 32 · Actors and Concurrency

729

An actor will only process messages matching one of the cases in the partial function passed to receive. For each message in the mailbox, receive will first invoke isDefinedAt on the passed partial function to determine whether it has a case that will match and handle the message. The receive method will choose the first message in the mailbox for which isDefinedAt returns true, and pass that message to the partial function’s apply method. The partial function’s apply method will handle the message. For example, echoActor’s apply method will print "received message: " followed by the message object’s toString result. If the mailbox contains no message for which isDefinedAt returns true, the actor on which receive was invoked will block until a matching message arrives.

For example, here is an actor that handles only messages of type Int:

scala> val intActor = actor { receive {

case x: Int => // I only want Ints println("Got an Int: "+ x)

}

}

intActor: scala.actors.Actor = scala.actors.Actor$$anon$1@34ba6b

If you send a String or Double, for example, the intActor will silently ignore the message:

scala> intActor ! "hello" scala> intActor ! math.Pi

But if you pass an Int, you’ll get a response printed out:

scala> intActor ! 12 Got an Int: 12

32.3 Treating native threads as actors

The actor subsystem manages one or more native threads for its own use. So long as you work with an explicit actor that you define, you do not need to think much about how they map to threads.

Cover · Overview · Contents · Discuss · Suggest · Glossary · Index

Section 32.4

Chapter 32 · Actors and Concurrency

730

The other direction is also supported: every native thread is also usable as an actor. However, you cannot use Thread.currentThread directly, because it does not have the necessary methods. Instead, you should use Actor.self if you want to view the current thread as an actor.

This facility is especially useful for debugging actors from the interactive shell. Here’s an example:

scala> import scala.actors.Actor._ import scala.actors.Actor._

scala> self ! "hello"

scala> self.receive { case x => x } res6: Any = hello

The receive method returns the value computed by the partial function passed to it. In this case, the partial function returns the message itself, and so the received message ends up being printed out by the interpreter shell.

If you use this technique, it is better to use a variant of receive called receiveWithin. You can then specify a timeout in milliseconds. If you use receive in the interpreter shell, then the receive will block the shell until a message arrives. In the case of self.receive, this could mean waiting forever! Instead, use receiveWithin with some timeout value:

scala> self.receiveWithin(1000) { case x => x } // wait a sec!

res7: Any = TIMEOUT

32.4 Better performance through thread reuse

Actors are implemented on top of normal Java threads. As described so far, in fact, every actor must be given its own thread, so that all the act methods get their turn.

Unfortunately, despite their light-sounding name, threads are not all that cheap in Java. Threads consume enough memory that typical Java virtual machines, which can host millions of objects, can have only thousands of threads. Worse, switching threads often takes hundreds if not thousands of processor cycles. If you want your program be as efficient as possible, then it is important to be sparing with thread creation and switching.

Cover · Overview · Contents · Discuss · Suggest · Glossary · Index

Section 32.4

Chapter 32 · Actors and Concurrency

731

To help you conserve threads, Scala provides an alternative to the usual receive method called react. Like receive, react takes a partial function. Unlike receive, however, react does not return after it finds and processes a message. Its result type is Nothing. It evaluates the message handler and that’s it.2

Because the react method does not need to return, the implementation does not need to preserve the call stack of the current thread. Instead, the library can reuse the current thread for the next actor that wakes up. This approach is so effective that if every actor in a program uses react instead of receive, only a single thread is necessary in principle to host all of the program’s actors (to be sure, if your computer has several processor cores, the actors subsystem will use enough threads to utilize all cores when it can).

In practice, programs will need at least a few receive’s, but you should try to use react whenever possible so as to conserve threads.

Because react does not return, the message handler you pass it must now both process that message and arrange to do all of the actor’s remaining work. A common way to do this is to have a top-level work method—such as act itself—that the message handler calls when it finishes. Listing 32.3 shows an example that uses this approach.

The actor shown in Listing 32.3 waits for strings that are host names, and if there is one, returns an IP address for that host name. Here’s an example:

scala> NameResolver.start()

res0: scala.actors.Actor = NameResolver$@90d6c5

scala> NameResolver ! ("www.scala-lang.org", self)

scala> self.receiveWithin(0) { case x => x }

res2: Any = Some(www.scala-lang.org/128.178.154.102)

scala> NameResolver ! ("wwwwww.scala-lang.org", self)

scala> self.receiveWithin(0) { case x => x } res4: Any = None

2Behind the scenes, react will throw an exception after its done.

Cover · Overview · Contents · Discuss · Suggest · Glossary · Index

Section 32.4

Chapter 32 · Actors and Concurrency

732

object NameResolver extends Actor {

import java.net.{InetAddress, UnknownHostException}

def act() { react {

case (name: String, actor: Actor) => actor ! getIp(name)

act()

case "EXIT" =>

println("Name resolver exiting.") // quit

case msg =>

println("Unhandled message: "+ msg) act()

}

}

def getIp(name: String): Option[InetAddress] = { try {

Some(InetAddress.getByName(name)) } catch {

case _:UnknownHostException => None

}

}

}

Listing 32.3 · An actor that calls react.

Writing an actor to use react instead of receive is challenging, but pays off in performance. Because react does not return, the calling actor’s call stack can be discarded, freeing up the thread’s resources for a different actor. At the extreme, if all of the actors of a program use react, then they can be implemented on a single thread.

This coding pattern is so common with event-based actors, there is special support in the library for it. The Actor.loop function executes a block of code repeatedly, even if the code calls react. NameResolver’s act method can be rewritten to use loop as shown in Listing 32.4. The one difference in behavior between this act method and that of Listing 32.3 is

Cover · Overview · Contents · Discuss · Suggest · Glossary · Index

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]