570 likes | 838 Views
Programming Paradigms for Concurrency. Lecture 10 Part III – Message Passing Concurrency. Message Passing Paradigms. Two important categories of MP paradigms: Actor or agent-based paradigms unique receivers : messages are sent directly from one thread to another Channel-based paradigms
E N D
Programming Paradigms for Concurrency Lecture 10 Part III – Message Passing Concurrency
Message Passing Paradigms Two important categories of MP paradigms: • Actor or agent-based paradigms • unique receivers: messages are sent directly from one thread to another • 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 B A A
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
Simple Example: Reference Cells signatureCELL = sig type 'a cell val cell : 'a -> 'a cell val read : 'a cell -> 'a val write : 'a cell -> 'a -> unit end 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 reqCh?READ 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) replyCh!x
Implementing read and write reqCh?READ fun read (CELL {reqCh, replyCh}) = (send (reqCh, READ); recvreplyCh) fun write (CELL {reqCh, ...}) x = send(reqCh, WRITE x) C1(x) C2(x) reqCh?WRITE(y) replyCh!x send : ('a chan* 'a) -> unit send (ch, x) : send value x over channel ch recv : 'a chan -> 'a recvch : receives a value from channel ch
Implementing cell reqCh?READ fun cell x = let valreqCh = channel () valreplyCh = channel () fun loop x = caserecvreqChof 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) channel : unit -> 'a chan channel () : creates a new channel replyCh!x spawn : (unit -> unit) -> unit spawn 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, ...}) = recvreadCh fun 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 valreadCh = channel () valwriteCh = channel () fun loop x = select [ wrap (sendEvt(readCh, x), fn () => loop x), wrap (recvEvtwriteCh, 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 fundoit request = ... (* the actual service *) • fun serverLoop () = • ifserviceAvailable () then • let valrequest = recvreqChin • 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() = ifserviceAvailable ()then let valrequest = recvreqChin send (replyCh, doit request); serverLoop () end else doSomethingElse () Solution: use procedural abstraction to hide protocol details on client side funclientCall x = (send (reqCh, x); recvreplyCh) • Problem: client blocks if service is not available • one would like to use selective communication in client, but procedural abstraction hides too much
First-Class Synchronous Events • Concurrent ML provides special event typetype ‘a event • Events decouple the description of synchronous operations from the actual act of synchronizing on the operation valsync : ‘a event -> ‘a • Event combinatorsenable construction of complex events that implement abstract protocols
Primitive Events valrecvEvt : ‘a chan -> ‘a event valsendEvt : ‘a chan * ‘a -> unit event recv and send are defined using these primitive events: funrecvch = sync (recvEvtch) funsend(ch, x) = sync (sendEvt(ch, x))
Post-Synchronization Actions: wrap valwrap : ‘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 valchoose : ‘a event list-> ‘a event choosees : returns an event that when synchronized, nondeterministically synchronizes one enabled event in es. funselectes = sync(choosees)
Simplified Cell Implementation (revisited) C(x) readCh?y writeCh!x fun cell x = let valreadCh = channel () valwriteCh = channel () fun loop x = select [ wrap (sendEvt(readCh, x), fn () => loop x), wrap (recvEvtwriteCh, loop) ] in spawn (fn () => loop x); CELL{readCh = readCh, writeCh = writeCh} end
Example: RPC Client (revisited) funclientCall x : ‘a -> ‘b option event = wrap (sendEvt (reqCh, x), fn () => SOME (recvreplyCh)) fun timeOut () : ‘b option event = let valto = 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 Swap channels enable symmetric communication between threads where values are exchanged in both directions. signatureSWAP_CHANNEL = sig type ‘a swap_chan valswapChannel : unit -> ‘a swap_chan val swap : (‘a swap_chan * ‘a) -> ‘a event end
Swap Channel Implementation (1st try) datatype ‘a swap_chan = SC of ‘a chan funswapChannel () = SC (channel ()) fun swap (SC ch, msgOut) = choose [ wrap (recvEvtch, fnmsgIn => (send(ch, msgOut); msgIn)), wrap (sendEvt (ch, msgOut), fn () => recvinCh) ]
Swap mismatch P1 P2 Q1 Q2 swap(ch, 1) swap(ch, 2) swap(ch, 3) swap(ch, 4) send(ch, 1) recvch send(ch, 3) recvch recvch send(ch, 2) recvch send(ch, 4) Race on shared channel!
Pre-Synchronization Actions: guard valguard : (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 funswapChannel () = SC (channel ()) fun swap (SC ch, msgOut) = guard (fn () => let valinCh = channel () in choose [ wrap (recvEvtch, fn (msgIn, outCh) => (send (outCh, msgOut); msgIn)), wrap (sendEvt (ch, (msgOut, inCh)), fn () => recvinCh) ] end) fresh private channel for each swap event
Example: RPC Server (revisited) fun serverLoop() = let valrequest = recvreqChin send (replyCh, doit request); serverLoop () end • funclientCall x = • guard (fn () => send (reqCh, x); recvEvtreplyCh) • fun client request = • select [clientCall request, timeoutEvent ()]
Client Timeout T C1 S C2 recvreqCh send(reqCh,1) atTimeEvt doit 42 send(replyCh,42) send(reqCh,2) deadlock Server S and other clients might deadlock if client C1 times out
Cancellation: withNack valwithNack : (unit event -> ‘a event) -> ‘a event • When withNackf is used in a select, the select evaluates fwith a fresh abort event nack. • The resulting event fnack is then used with the other events in the select. • If the select does not choose fnack when it is synchronized, then nack is synchronized, effectively informing fnack that it is not chosen.
Example: RPC Server (revisited) fun serverLoop() = let val(request, nack) = recvreqChin select [sendEvt (replyCh, doit request),nack]; serverLoop () end • funclientCall x = withNack (fn nack => • send (reqCh, (x, nack)); recvEvtreplyCh) • fun client request = • select [clientCall request, timeoutEvent ()]
Client Timeout T C1 S C2 recvreqCh send(reqCh,(1,na)) atTimeEvt doit 42 na na send(reqCh,(2,na)) recvreqCh
More Features of Concurrent ML • more event combinators • buffered channels • multicast channels • sync variables • built-in RPC support • ...
Event Represenation Canonicalform 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 choose choose wrap wrap wrap recv choose recv wrap wrap wrap wrap wrap wrap send recv send recv
Base Event Representation Each base event is a record of three functions: pollFn doFn blockFn 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: • Polling Phase: poll the base events using pollFn to see if any of them are enabled • Commit Phase: if one or more base events are enabled, pick one and synchronize on it using doFn • Blocking Phase: if no base events are enabled: • enqueue a continuation for the calling thread on each of the base events using its blockFn • switch to some other thread • eventually, some other thread will complete the synchronization.
First-Class Continuations type ‘a cont valcallcc : (‘a cont -> ‘a) -> ‘a val throw : ‘a cont -> ‘a -> ‘b
Event Representation datatypeevent_status = INIT | WAITING | SYNCHED typeevent_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 sync funsyncev = callcc (fnresumeK => let fun poll (ev, enabled) = ... fundoEvt enabled = ... andblockThd () = ... in doEvt (poll (ev, [])) end)
Implementing sync funsyncev = callcc (fnresumeK => let fun poll (BEVT{pollFn, doFn, ...}, enabled) = ifpollFn () thendoFn::enabled else enabled | poll (CHOOSE(ev1, ev2)), enabled) = poll(ev2, poll(ev1, enabled)) fundoEvt enabled = ... andblockThd () = ... in doEvt (poll (ev, [])) end)
Implementing sync funsyncev = callcc (fnresumeK => let fun poll (ev, enabled) = ... fundoEvt [] = blockThd () | doEvt (doFn::enabled) = doFnresumeK; (* if doFn terminates then sync attempt failed, so continue *) doEvt enabled andblockThd () = ... in doEvt (poll (ev, [])) end)
Implementing sync funsyncev = callcc (fnresumeK => let fun poll (ev, enabled) = ... fundoEvt enabled = ... andblockThd () = let valflg = 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 wrap funwrap (BEVT {pollFn, doFn, blockFn}, f) = BEVT { pollFn = pollFn, doFn = fn k => callcc (fnretK => throw k (f (callcc (fn k’ => doFn k’; throw retK ()))))), blockFn = fn (flg, k) => callcc (fnretK => 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 recvEvt funrecvEvt (CH {lock, sendq, recvq}) = let fun pollFn () = ... fundoFn k = ... funblockFn (flg : event_state, k) = ... in BEVT {pollFn = pollFn, doFn = doFn, blockFn = blockFn} end