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

Chapter 32

Actors and Concurrency

Sometimes it helps in designing a program to specify that things happen independently, in parallel, concurrently. Java includes support for concurrency, and although this support is sufficient, it turns out to be quite difficult to get right in practice as programs get larger and more complex. Scala augments Java’s native support by adding actors. Actors provide a concurrency model that is easier to work with and can, therefore, help you avoid many of the difficulties of using Java’s native concurrency model. This chapter will show you the basics of how to use Scala’s actors library and provide an extended example that transforms the single-threaded circuit simulation code of Chapter 18 into a multi-threaded version.

32.1 Trouble in paradise

The Java platform comes with a built-in threading model based on shared data and locks. Each object is associated with a logical monitor, which can be used to control multi-threaded access to data. To use this model, you decide what data will be shared by multiple threads and mark as “synchronized” sections of the code that access, or control access to, the shared data. The Java runtime employs a locking mechanism to ensure that only one thread at a time enters synchronized sections guarded by the same lock, thereby enabling you to orchestrate multi-threaded access to the shared data.

Unfortunately, programmers have found it very difficult to reliably build robust multi-threaded applications using the shared data and locks model, especially as applications grow in size and complexity. The problem is that at each point in the program, you must reason about what data you are mod-

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

Section 32.2

Chapter 32 · Actors and Concurrency

725

ifying or accessing that might be modified or accessed by other threads, and what locks are being held. At each method call, you must reason about what locks it will try to hold, and convince yourself that it will not deadlock while trying to obtain them. Compounding the problem, the locks you reason about are not fixed at compile time, because the program is free to create new locks at run time as it progresses.

Making things worse, testing is not reliable with multi-threaded code. Since threads are non-deterministic, you might successfully test a program one thousand times, yet still the program could go wrong the first time it runs on a customer’s machine. With shared data and locks, you must get the program correct through reason alone.

Moreover, you can’t solve the problem by over-synchronizing either. It can be just as problematic to synchronize everything as it is to synchronize nothing. The problem is that new lock operations remove possibilities for race conditions, but simultaneously add possibilities for deadlocks. A correct lock-using program must have neither race conditions nor deadlocks, so you cannot play it safe by overdoing it in either direction.

Java 5 introduced java.util.concurrent, a library of concurrency utilities that provides higher level abstractions for concurrent programming. Using the concurrency utilities makes multi-threaded programming far less error prone than rolling your own abstractions with Java’s low-level synchronization primitives. Nevertheless, the concurrent utilities are also based on the shared data and locks model, and as a result do not solve the fundamental difficulties of using that model.

Scala’s actors library does address the fundamental problem by providing an alternative, share-nothing, message-passing model that programmers tend to find much easier to reason about. Actors are a good first tool of choice when designing concurrent software, because they can help you avoid the deadlocks and race conditions that are easy to fall into when using the shared data and locks model.

32.2 Actors and message passing

An actor is a thread-like entity that has a mailbox for receiving messages. To implement an actor, you subclass scala.actors.Actor and implement the act method. An example is shown in Listing 32.1. This actor doesn’t do anything with its mailbox. It just prints a message five times and quits.

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

Section 32.2

Chapter 32 · Actors and Concurrency

726

import scala.actors._

object SillyActor extends Actor { def act() {

for (i <- 1 to 5) { println("I'm acting!") Thread.sleep(1000)

}

}

}

Listing 32.1 · A simple actor.

You start an actor by invoking its start method, similar to the way you start a Java thread:

scala> SillyActor.start() I'm acting!

res4: scala.actors.Actor = SillyActor$@1945696

scala> I'm acting! I'm acting!

I'm acting! I'm acting!

Notice that the “I’m acting!” output is interleaved with the Scala shell’s output. This interleaving is due to the SillyActor actor running independently from the thread running the shell. Actors run independently from each other, too. For example, given this second actor:

import scala.actors._

object SeriousActor extends Actor { def act() {

for (i <- 1 to 5) {

println("To be or not to be.") Thread.sleep(1000)

}

}

}

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

Section 32.2

Chapter 32 · Actors and Concurrency

727

You could run two actors at the same time, like this:

scala> SillyActor.start(); SeriousActor.start() res3: scala.actors.Actor = seriousActor$@1689405

scala> To be or not to be. I'm acting!

To be or not to be. I'm acting!

To be or not to be. I'm acting!

To be or not to be. I'm acting!

To be or not to be. I'm acting!

You can also create an actor using a utility method named actor in object scala.actors.Actor:

scala> import scala.actors.Actor._

scala> val seriousActor2 = actor { for (i <- 1 to 5)

println("That is the question.") Thread.sleep(1000)

}

scala> That is the question. That is the question.

That is the question. That is the question. That is the question.

The val definition above creates an actor that executes the actions defined in the block following the actor method. The actor starts immediately when it is defined. There is no need to call a separate start method.

All well and good. You can create actors and they run independently. How do they work together, though? How do they communicate without using shared memory and locks? Actors communicate by sending each other messages. You send a message by using the ! method, like this:

scala> SillyActor ! "hi there"

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

Section 32.2

Chapter 32 · Actors and Concurrency

728

Nothing happens in this case, because SillyActor is too busy acting to process its messages, and so the "hi there" message sits in its mailbox unread. Listing 32.2 shows a new, more sociable, actor that waits for a message in its mailbox and prints out whatever it receives. It receives a message by calling receive, passing in a partial function.1

val echoActor = actor { while (true) {

receive { case msg =>

println("received message: "+ msg)

}

}

}

Listing 32.2 · An actor that calls receive.

When an actor sends a message, it does not block, and when an actor receives a message, it is not interrupted. The sent message waits in the receiving actor’s mailbox until the actor calls receive. You can see this behavior illustrated here:

scala> echoActor ! "hi there" received message: hi there

scala> echoActor ! 15

scala> received message: 15

As discussed in Section 15.7, a partial function (an instance of trait PartialFunction) is not a full function—i.e., it might not be defined over all input values. In addition to an apply method that takes one argument, a partial function offers an isDefinedAt method, which also takes one argument. The isDefinedAt method will return true if the partial function can “handle” the passed value. Such values are safe to pass to apply. If you pass a value to apply for which isDefinedAt would return false, however, apply will throw an exception.

1As described in Section 15.7, a partial function literal is expressed as a series of match alternatives or “cases.” It looks like a match expression without the match keyword.

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

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