Message Passing Paradigms
Two important categories of MP paradigms:
1. Actor or agent-based paradigms- unique receivers: messages are sent directly from
one thread to another
2. Channel-based paradigms– multiple receivers: messages are sent to channels
that are shared between threads
Channel-based Message Passing
• threads perform local computations and synchronize via MP over channels
• channels can be shared between threads(no unique receiver)
• communication is typically synchronous (blocking send)
• threads are anonymous • threads and channels are created
dynamically
A
A
B
A Brief History of Channel-Based Paradigms
• Hoare 1978, 1985: Communicating sequential processes (CSP)• Milner 1980: Calculus of communicating systems
(CCS)• May et al. 1985: Occam language• Reppy 1991: Concurrent ML• Milner, Parrow, Walker 1992: ¼ calculus• Gropp, Lusk, Skjellum 1994: Message passing
interface (MPI)• Griesemer, Pike, Thompson 2009: Go language
Concurrent ML
• higher-order concurrent language• extension of Standard ML• part of the SML of New Jersey language
distribution: http://www.smlnj.org/• first-class events: provides special event type and
event combinators enables building protocol abstractions
• threads and channels are garbage collected supports speculative communication
signature CELL = sig type 'a cell val cell : 'a -> 'a cell val read : 'a cell -> 'a val write : 'a cell -> 'a -> unit
end
Simple Example: Reference Cells
cell x : creates new cell holding value x read c : retrieves value stored in cell c write c x: updates cell c to hold value x
Cell implementation
• each cell has an associated thread holding the stored value
• each cell encapsulates two channels to its associated thread for updating/retrieving the stored value
Cell Implementation
datatype 'a request = READ | WRITE of 'a
datatype 'a cell = CELL of { reqCh : 'a request chan, replyCh : 'a chan}
C1(x) C2(x)reqCh?WRITE(y)
reqCh?READ
replyCh!x
Implementing read and write
fun read (CELL {reqCh, replyCh}) = (send (reqCh, READ); recv replyCh) fun write (CELL {reqCh, ...}) x = send(reqCh, WRITE x)
C1(x) C2(x)reqCh?WRITE(y)
reqCh?READ
replyCh!xsend : ('a chan * 'a) -> unitsend (ch, x) : send value x over channel ch
recv : 'a chan -> 'arecv ch : receives a value from channel ch
Implementing cell
fun cell x = let val reqCh = channel ()
val replyCh = channel () fun loop x = case recv reqCh of
READ => (send (replyCh, x); loop x) | WRITE y => loop y in
spawn (fn () => loop x); CELL{reqCh = reqCh, replyCh = replyCh}
end
C1(x) C2(x)reqCh?WRITE(y)
reqCh?READ
replyCh!x
channel : unit -> 'a chanchannel () : creates a new channel
spawn : (unit -> unit) -> unitspawn f : creates a new thread executing f
Selective Communication
Allow a process to block on a nondeterministic choice of several blocking communications– the first communication that becomes enabled is
chosen– if two or more communications are available
simultaneously, then one is chosen nondeterministically.
Simplified Cell Implementation
datatype 'a cell = CELL of { readCh : 'a chan, writeCh : 'a chan}
fun read (CELL{readCh, ...}) = recv readChfun write (CELL{writeCh, ...}) = send
(writeCh, x)
C(x)readCh?y writeCh!x
Simplified Cell Implementation
C(x)readCh?y writeCh!x
fun cell x = let val readCh = channel () val writeCh = channel () fun loop x = select [ wrap (sendEvt(readCh, x), fn () => loop x), wrap (recvEvt writeCh, loop) ] in
spawn (fn () => loop x); CELL{readCh = readCh, writeCh = writeCh}
end
First-Class Events
Design principle for MP programming• use communication primitives to implement
complex protocols• use abstraction to hide actual communication
channels and thus details of thread interactions implement abstract protocols
Problem: standard abstraction mechanisms provided by sequential languages hide too much.
Example: RPC Server
• Server provides service via a remote procedure call (RPC) protocol
• RPC protocol:– a client must send a request before trying to read
a reply– following a request, a client must read exactly one
reply before issuing another request
Example: RPC Server
fun doit request = ... (* the actual service *)
fun serverLoop () = if serviceAvailable () then let val request = recv reqCh in send (replyCh, doit request); serverLoop () end else doSomethingElse ()
Problem: if some client does not respect the RPC protocol, then the server may get out of sync.
Example: RPC Server
fun serverLoop () = if serviceAvailable () then let val request = recv reqCh in send (replyCh, doit request); serverLoop () end else doSomethingElse ()
Solution: use procedural abstraction to hide protocol details on client side
fun clientCall x = (send (reqCh, x); recv replyCh)
Problem: client blocks if service is not availableone would like to use selective communication in client,
but procedural abstraction hides too much
First-Class Synchronous Events
• Concurrent ML provides special event type
type ‘a event
• Events decouple the description of synchronous operations from the actual act of synchronizing on the operation
val sync : ‘a event -> ‘a
• Event combinators enable construction of complex events that implement abstract protocols
Primitive Events
val recvEvt : ‘a chan -> ‘a eventval sendEvt : ‘a chan * ‘a -> unit event
recv and send are defined using these primitive events:
fun recv ch = sync (recvEvt ch)fun send (ch, x) = sync (sendEvt (ch, x))
Post-Synchronization Actions: wrap
val wrap : ‘a event * (‘a -> ‘b) -> ‘b event
wrap (e, f) : returns an event that when synchronized,synchronizes e and applies f to the result
Selective Synchronization: choose
val choose : ‘a event list-> ‘a event
choose es : returns an event that when synchronized, nondeterministically synchronizes one enabled event in es.
fun select es = sync (choose es)
Simplified Cell Implementation (revisited)
C(x)readCh?y writeCh!x
fun cell x = let val readCh = channel () val writeCh = channel () fun loop x = select [ wrap (sendEvt(readCh, x), fn () => loop x), wrap (recvEvt writeCh, loop) ] in
spawn (fn () => loop x); CELL{readCh = readCh, writeCh = writeCh}
end
Example: RPC Client (revisited)
fun clientCall x : ‘a -> ‘b option event = wrap (sendEvt (reqCh, x), fn () => SOME (recv
replyCh))
fun timeOut () : ‘b option event = let val to = Time.now + Time.fromSeconds 60 in wrap (atTimeEvt to, fn () => NONE) end
fun client request : ‘a -> ‘b option = select [clientCall request, timeOut ()]
Example: Swap Channels
signature SWAP_CHANNEL = sig type ‘a swap_chan val swapChannel : unit -> ‘a swap_chan val swap : (‘a swap_chan * ‘a) -> ‘a event end
Swap channels enable symmetric communication between threads where values are exchanged in both directions.
Swap Channel Implementation (1st try)
datatype ‘a swap_chan = SC of ‘a chan fun swapChannel () = SC (channel ())
fun swap (SC ch, msgOut) = choose [ wrap (recvEvt ch, fn msgIn => (send(ch, msgOut); msgIn)), wrap (sendEvt (ch, msgOut), fn () => recv inCh) ]
Swap mismatchP1 P2 Q1 Q2
swap(ch, 1) swap(ch, 2)swap(ch, 3) swap(ch, 4)
send(ch, 1) recv ch
send(ch, 3) recv ch
send(ch, 2) recv ch
send(ch, 4)recv ch
Race on shared channel!
Pre-Synchronization Actions: guard
val guard : (unit -> ‘a event) -> ‘a event
guard f: returns an event that when synchronized, first evaluates f and then synchronizes the resulting event
Swap Channel Implementation
datatype ‘a swap_chan = SC of (‘a * ‘a chan) chan fun swapChannel () = SC (channel ())
fun swap (SC ch, msgOut) = guard (fn () => let val inCh = channel () in choose [ wrap (recvEvt ch, fn (msgIn, outCh) => (send (outCh, msgOut); msgIn)), wrap (sendEvt (ch, (msgOut, inCh)), fn () => recv inCh) ] end)
fresh private channel for each swap event
Example: RPC Server (revisited)
fun serverLoop () = let val request = recv reqCh in send (replyCh, doit request); serverLoop () end
fun clientCall x = guard (fn () => send (reqCh, x); recvEvt
replyCh)
fun client request = select [clientCall request, timeoutEvent ()]
Client TimeoutT C1 S C2
send(reqCh,1) recv reqCh
send(replyCh,42)
doit 42
atTimeEvt
Server S and other clients might deadlock if client C1 times out
send(reqCh,2)
deadlock
Cancellation: withNack
val withNack : (unit event -> ‘a event) -> ‘a event
• When withNack f is used in a select, the select evaluates f with a fresh abort event nack.
• The resulting event f nack is then used with the other events in the select.
• If the select does not choose f nack when it is synchronized, then nack is synchronized, effectively informing f nack that it is not chosen.
Example: RPC Server (revisited)
fun serverLoop () = let val (request, nack) = recv reqCh in select [sendEvt (replyCh, doit request),
nack]; serverLoop () end
fun clientCall x = withNack (fn nack => send (reqCh, (x, nack)); recvEvt replyCh)
fun client request = select [clientCall request, timeoutEvent ()]
Client TimeoutT C1 S C2
send(reqCh,(1,na)) recv reqCh
doit 42
atTimeEvt
send(reqCh,(2,na))
na na
recv reqCh
More Features of Concurrent ML
• more event combinators• buffered channels• multicast channels• sync variables• built-in RPC support• ...
Event Represenation
Canonical form of an event is a binary tree with:• wrapped base-event values as leaves• choice operators as internal nodes
Canonical form is obtained using the following equivalences: wrap(wrap(ev,g),f) = wrap(ev, f ± g)wrap(choose(ev1,ev2),f) =
choose(wrap(ev1,f), wrap(ev2,f))
Event Represenation
choose
wrap
recv
wrap
choose
wrap wrap
recvsend
choose
wrap
recv
wrap wrap
recvsend
choose
wrapwrap
Base Event Representation
pollFn
doFn
blockFn
Each base event is a record of three functions:
test if base event is enabled
synchronize on the enabled base event
block calling thread on the base event
Protocol for sync
Each call to sync from a PCL thread executes the following protocol:1. Polling Phase: poll the base events using pollFn to see if
any of them are enabled2. Commit Phase: if one or more base events are enabled,
pick one and synchronize on it using doFn3. Blocking Phase: if no base events are enabled:
a. enqueue a continuation for the calling thread on each of the base events using its blockFn
b. switch to some other threadc. eventually, some other thread will complete the synchronization.
First-Class Continuations
type ‘a cont
val callcc : (‘a cont -> ‘a) -> ‘aval throw : ‘a cont -> ‘a -> ‘b
Event Representation
datatype event_status = INIT | WAITING | SYNCHED
type event_state = event_status ref
datatype ‘a evt = BEVT of { pollFn : unit -> bool doFn : ‘a cont -> unit blockFn : (event_state * ‘a cont) -> unit } | CHOOSE of ‘a evt * ‘a evt
Implementing syncfun sync ev = callcc (fn resumeK => let fun poll (ev, enabled) = ... fun doEvt enabled = ... and blockThd () = ... in doEvt (poll (ev, [])) end)
Implementing syncfun sync ev = callcc (fn resumeK => let fun poll (BEVT{pollFn, doFn, ...}, enabled) = if pollFn () then doFn::enabled else enabled | poll (CHOOSE(ev1, ev2)), enabled) = poll(ev2, poll(ev1, enabled)) fun doEvt enabled = ... and blockThd () = ... in doEvt (poll (ev, [])) end)
Implementing syncfun sync ev = callcc (fn resumeK => let fun poll (ev, enabled) = ... fun doEvt [] = blockThd () | doEvt (doFn::enabled) = doFn resumeK; (* if doFn terminates then sync attempt failed, so continue *) doEvt enabled and blockThd () = ... in doEvt (poll (ev, [])) end)
Implementing syncfun sync ev = callcc (fn resumeK => let fun poll (ev, enabled) = ... fun doEvt enabled = ... and blockThd () = let val flg = ref INIT fun block (BEVT {blockFn, ...}) = blockFn (flg, resumeK) block (CHOOSE (ev1, ev2)) = (block ev1; block ev2) in block ev; flg := WAITING; dispatch() end in doEvt (poll (ev, [])) end)
Implementing wrapfun wrap (BEVT {pollFn, doFn, blockFn}, f) = BEVT { pollFn = pollFn, doFn = fn k => callcc (fn retK => throw k (f (callcc (fn k’ => doFn k’; throw retK ()))))), blockFn = fn (flg, k) => callcc (fn retK => throw k (f (callcc (fn k’ => blockFn(flg, k’); throw retK ()))))) | wrap (CHOOSE(ev1, ev2), f) = CHOOSE(wrap(ev1, f), wrap(ev2, f))
Event Representation
datatype ‘a chan = CH of { lock : bool ref, sendq : (‘a * unit cont) queue recvq : (event_state * ‘a cont) queue }
Implementing recvEvtfun recvEvt (CH {lock, sendq, recvq}) = let fun pollFn () = ... fun doFn k = ... fun blockFn (flg : event_state, k) = ... in BEVT {pollFn = pollFn, doFn = doFn, blockFn = blockFn} end
Implementing recvEvtfun recvEvt (CH {lock, sendq, recvq}) = let fun pollFn () = not(isEmptyQ(sendq)) fun doFn k = ... fun blockFn (flg : event_state, k) = ... in BEVT {pollFn = pollFn, doFn = doFn, blockFn = blockFn} end
Implementing recvEvtfun recvEvt (CH {lock, sendq, recvq}) = let fun pollFn () = ... fun doFn k = let val _ = spinLock lock val item = dequeue sendq in spinUnlock lock; case item of NONE => () | SOME(msg, sendK) => (enqueueRdy sendK; throw k msg) end fun blockFn (flg : event_state, k) = ... in BEVT {...} end
Implementing recvEvtfun recvEvt (CH {lock, sendq, recvq}) = let fun pollFn () = ... fun doFn k = ... fun blockFn (flg : event_state, k) = spinLock lock; case dequeue sendq of SOME (msg, sendK) => ( spinUnlock lock; flg := SYNCHED; enqueueRdy sendK; throw k msg ) | NONE => ( enqueue (recvq, (flg, k)); spinUnlock lock ) end in BEVT {...} end
Further Reading
For more details about CML see• CML: A higher-order concurrent language.
J. Reppy. In PLDI 1991.• Concurrent Programming in ML.
J. Reppy, Cambridge University Press, 1999.• Toward a parallel implementation of
Concurrent ML. J. Reppy and Y. Xiao. In DAMP 2008.
• Parallel Concurrent ML.J. Reppy, C. Russo, and Y. Xiao. In ICFP 2009.
Project Proposals
1. Performance/Conciseness Evaluation of Concurrent Programming ParadigmsCompare idiomatic implementations of the same algorithm in Scala using different programming paradigms:– a sequential implementation– a concurrent implementation based on actors– a concurrent implementation based on shared
memory
Project Proposals
2. First-Class Synchronous Events in Scala– Take inspiration from the Scala Actor Library and
the CML library– Implement a Scala library supporting first-class
synchronous events.
Project Proposals
3. Verification of Scala Actor Programs– Implement a non-trivial Scala Actor program or
take an existing one – Manually compute finite-state abstractions of all
actors– Verify properties of the abstracted program using a
formal verification tool for MP programs
Please choose your project until 12/22/2010!