520 likes | 686 Views
Programming Paradigms for Concurrency. Lecture 9 Part III – Message Passing Concurrency. Classical Shared Memory Concurrency - The Downsides. shared memory typically implies physically shared memory l ocks: the “ goto statements ” of concurrency
E N D
Programming Paradigms for Concurrency Lecture 9 Part III – Message Passing Concurrency
Classical Shared Memory Concurrency -The Downsides • shared memory typically implies physicallyshared memory • locks: the “goto statements” of concurrency • OS threads are resource-hungry and context-switching is expensive • no natural adaptation to distributed computing • reasoning about programs is (even more) difficult • number of threads = number of available cores • ≠ number of logical tasks
Message Passing Concurrency • no shared-memory (in its pure form) + some classes of concurrency errors avoided by design + natural programming model for distributed architectures - less efficient on shared-memory architectures: data must be copied before sending • synchronization between processes is explicit + reasoning about program behavior is simplified - some say: it’s harder to parallelize a sequential program using MP • higher level of abstraction + decoupling between computation tasks and physical threads possible -> event-based programming
Message Passing Paradigms Two important categories of MP paradigms: • Actor or agent-based paradigms • unique receivers: messages are sent directly from one process to another • Channel-based paradigms • multiple receivers: messages are sent to channels that are shared between processes We will look at one programming language in each category.
Reading Material • Actors in Scala. Haller and Sommers, Artima, to appear 2011 (preprint available). • Concurrent Programming in ML. Reppy, Cambridge University Press, 1999. • Communicating and Mobile Systems: The Pi Calculus. Milner, Cambridge University Press, 1999. Additional material will be posted on the lecture web site.
The Actor Paradigm Actors are the object-oriented approach to concurrency “everything is an actor” actor = object + logical thread
A Brief History of Actors • Hewitt, Bishop, Steiger 1973: proposal of actors • Greif 1975: operational semantics • Baker, Hewitt 1977: axiomatic semantics • Lieberman, Theriault 1981: Act-1 language • Clinger 1981: denotational semantics • Agha 1986: transition semantics • … • Armstrong et al. 1990s: Erlang language • … • Haller, Odersky 2007: actors in the Scala language
Actors in a Nutshell • actors perform local computations and communicate via MP • communication is • asynchronous • buffered (unordered in theory but FIFO in practice) • over unique-receiver channels (mailboxes) • restricted to “known” actors • computation is • even-driven: react to incoming messages • dynamically create other actors • send messages to other actors • dynamically change behavior • languages supporting actor-based concurrency • Erlang • Salsa • Scala • many implementations in form of libraries B A A
The Scala Language • unifies object-oriented and functional programming concepts • mixin class composition • higher-order functions • algebraic data types + pattern matching • closures • statically typed (type system based on System F) • interoperable with Java (compiles to the JVM) • enables embedding of rich domain-specific languages • Open-source: available from http://www.scala-lang.org
Scala Actors[Haller, Odersky, 2007] • Scala library extension for high-level concurrent programming • part of the Scala standard library • pair of message receive operations (receive/react) • allows trade-off between efficiency and flexibility • react enables event-based programming without inversion of control • message handlers as first-class partial functions • enables extension of actor behavior • wide adoption • Lift web framework • Twitter
Actor Chat ChatClient ChatRoom session ChatClient ChatClient ChatClient private state User subscribe to a chat room to receive chat messages. The chat room maintains a session of subscribers.
Defining Actors importscala.actors.Actor classChatRoomextends Actor { def act() { // the actor’s behavior } } Actors are regular Scala objects that extend the Actor trait.
Creating and Starting Actors object main extends Application{ valchatRoom = new ChatRoom chatRoom.start() // calls chatRoom.act }
Communication in Actor Chat Subscribe user: User ChatRoom session Unsubscribe user: User ChatClient UserPost user: User post: Post private state private state All communication is via message passing.
Messages case class User(name: String) case class Post(msg: String) abstract class Msg case class Subscribe(user: User) extendsMsg case class Unsubscribe(user: User) extendsMsg case class UserPost(user: User, post: Post) extendsMsg Any Scala object can serve as a message Good practice: use immutable case classes
Defining the act Method classChatRoomextends Actor { def act() { while (true) { receive { case Subscribe(user) => // handle subscription message case Unsubscribe(user) => // handle unsubscribe request caseUserPost(user, post) => // handle a post from a user } } } } Defines a closure f • Actor reacts to incoming message via the receivemethod of the Actor trait • receive takes a partial function f as argument
Defining the act Method classChatRoomextends Actor { def act() { while (true) { receive { case Subscribe(user) => // handle subscription message case Unsubscribe(user) => // handle unsubscribe request caseUserPost(user, post) => // handle a post from a user } } } } Defines a closure f • f maps incoming messages to the corresponding action performed by the actor • f is matched against the messages in the actor’s mailbox
Defining the act Method classChatRoomextends Actor { def act() { while (true) { receive { case Subscribe(user) => // handle subscription message case Unsubscribe(user) => // handle unsubscribe request caseUserPost(user, post) => // handle a post from a user } } } } Defines a closure f • the first message on which f is defined is received • unmatched messages remain in the mailbox • if no message matches, receive blocks until a matching message is received
Inplace Actor Definitions valchatRoom = actor { while (true) { receive { case Subscribe(user) => // handle subscription message case Unsubscribe(user) => // handle unsubscribe request caseUserPost(user, post) => // handle a post from a user } } } closure defines act method of chatRoom
Handling Subscriptions varsession = Map.empty[Actor, Actor] while (true) { receive { case Subscribe(user) => valsessionHandler= actor { while (true) { self.receive { case Post(msg) => // forward message to subscribers } } } session = session + (subsriber-> sessionHandler) // handle UserPost and Unsubscribe message } }
Sending Messages valchatRoom = newChatRoom chatRoom! Subscribe(User("Bob")) Method ! asynchronously sends a message to the recipient.
Sending Messages valchatRoom = newChatRoom chatRoom!? Subscribe(User("Bob")) match { case response: String => println(response) } Method !? asynchronously sends a message and then blocks until a reply message has been received from the recipient.
Handling Subscriptions varsession = Map.empty[Actor, Actor] while (true) { receive { case Subscribe(user) => val subscriber = sender valsessionHandler= actor { while (true) { self.receive { case Post(msg) => subscriber ! Post(msg) } } } session = session + (subscriber -> sessionHandler) reply(“subscribed: “ + user.name) // handle UserPost and Unsubscribe message } } address of the sender of matched message sends message to sender
Message Timeouts with receiveWithin varsession = Map.empty[Actor, Actor] while (true) { receive { case Subscribe(user) => val (subscriber, room) = (sender, self) valsessionHandler= actor { while (true) { self.receiveWithin(1800 * 1000) { case Post(msg) => for (key <- session.keys; if key != subscriber) session(key) !Post(msg) case TIMEOUT => room ! Unsubscribe(user) case‘die => self.exit() } } } ... } }
Processing Remaining Messages varsession = Map.empty[Actor, Actor] while (true) { receive { // handle Subscribe message case Unsubscribe(user) => session(sender) ! ‘die session = session – sender caseUserPost(user, msg) => session(sender) !msg } }
Remote Actors Remote actors enable transparent communication between Scala actors over networks import scala.actors.Actor._ import scala.actors.remote.RemoteActor._ classChatRoom(port: Int) extends Actor { def act() { alive(port) register(‘chatRoom, self) // ... } } attach this actor to given port register given symbol with given actor
Remote Actors Remote actors enable transparent communication between Scala actors over networks import scala.actors.Actor._ import scala.actors.remote.RemoteActor._ classChatClient(chatURL: String, chatPort: Int) extends Actor { def act() { val node = Node(chatURL, chatPort) valchatRoom = select(node, ‘chatRoom) chatRoom! Subscribe(User(“Bob”)) // ... } } obtain local interface to remote actor
Event-Based Programming receive binds an actor to a dedicated JVM thread • if receive blocks, so does the dedicated thread of the blocking actor • each actor (blocking or nonblocking) needs its own thread • scales badly with the number of actors, since JVM threads are resource hungry Scala actors provide an alternative to receive, which enables event-based programming.
receive vs. react react behaves like receive but with a different waiting strategy. • if react blocks, the actor is suspended and its dedicated thread released for processing events of other actors • event-based programming with react scales to large numbers of actors
Event-Based Programming with react classChatRoomextends Actor { def act() { while (true) { react { case Subscribe(user) => // handle subscription message case Unsubscribe(user) => // handle unsubscribe request caseUserPost(user, post) => // handle a post from a user } } } } never returns!
Event-Based Programming with react classChatRoomextends Actor { def act(): Unit = { react { case Subscribe(user) => // handle subscription message act() case Unsubscribe(user) => // handle unsubscribe request act() caseUserPost(user, post) => // handle a post from a user act() } } } closure must encompass full continuation of the actor
Or Simpler... classChatRoomextends Actor { def act() { loop{ react { case Subscribe(user) => // handle subscription message case Unsubscribe(user) => // handle unsubscribe request caseUserPost(user, post) => // handle a post from a user } } } } behaves like while (true) { receive { ... }} special combinators for composing react blocks
Lightweight Execution Environment Actors (many) Task queue Worker threads (few)
Creating Actors actor { // body } closure => T3 T1 T2 T3 Events generate tasks
Suspend in Event Mode Exception unwinds stack of actor/worker thread finishes current task Task Ti ... react { caseMsg(x) => // handle msg } defreact(f: PartialFunction[Any, Unit]): Nothing = { mailbox.dequeueFirst(f.isDefinedAt) match { case None => continuation = f; suspended = true case Some(msg) => ... } throw new SuspendActorException }
Resume in Event Mode Actor asuspended with { caseMsg(x) => // handle msg } w.t. executes Ti Ti+2 Ti+1 Task Ti: ... a!Msg(42) ... Task Ti+2: .apply(Msg(42))
Code Hot Swapping • payload of a message can be arbitrary scala object • actors can send new behavior to other actors • enables dynamic reconfiguration of actor behavior
Hot Swapping Server case classHotSwap(code: PartialFunction[Any, Unit]) class Server extends Actor { def act = loop { react { genericBaseorElseactorBase } } private defactorBase: PartialFunction[Any, Unit] = hotswapgetOrElse body private varhotswap: Option[PartialFunction[Any, Unit]] = None private valgenericBase: PartialFunction[Any, Unit] = { caseHotSwap(code) => hotswap = code } def body: PartialFunction[Any, Unit] = { ... } }
Hot Swapping Server case classHotSwap(code: PartialFunction[Any, Unit]) class Server extends Actor { ... private valgenericBase: PartialFunction[Any, Unit] = { caseHotSwap(code) => hotswap = Some(code) } } Hot swapping the server: case object Ping ... val server = new Server server.start server !HotSwap({case Ping => println(“Ping”)}) server ! Ping
Actors and Exceptions • act method of an actor may throw exceptions • unhandled exceptions do not seep out of the throwing actor • but unhandled exceptions terminate the throwing actor • other actors might wait for messages from actors that died silently • may cause deadlocks that are hard to debug
Simple Exception Handling object A extends Actor { def act() { react { case 'hello => throw new Exception("Error!") } } override def exceptionHandler = { case e: Exception => println(e.getMessage()) } } scala> A.start() scala> A ! 'hello Error!
Monitoring Actors Actor library provides special support for monitoring the life cycle of (a group of) actors: • exit(reason): terminates this actor • link(otherActor): links this actor with otherActor In case of termination/uncaught exceptions, exit is called implictely. Calls to exit are propagated to all linked actors. Enables • error propagation • delegated error handling • fault tolerance
Error Propagation object Master extends Actor { def act() { Slave ! 'doWork receive { case 'done => throw new Exception("Master crashed") } } } object Slave extends Actor { def act() { link(Master) while (true) { receive { case 'doWork => println("Done") reply('done) } } } } Slave terminates, if Master crashes or terminates
Delegated Error Handling val a = actor { receive { case 'start => valsomethingBadHappened = ... // some error condition if (somethingBadHappened) throw new Exception("Error!") println("Nothing bad happened") } } val b = actor { self.trapExit = true link(a) a ! 'start receive { case Exit(from, reason) if from == a => println("Actor 'a' terminated because of " + reason) } } calls to a’sexit method are converted to Exit messages in b’s mailbox
More Features of Scala Actors • Futures • Pluggable schedulers • Unique references: race-free imperative actors • ...