+ All Categories
Home > Documents > Programming Paradigms for Concurrency Lecture 10 Part III – Message Passing Concurrency.

Programming Paradigms for Concurrency Lecture 10 Part III – Message Passing Concurrency.

Date post: 31-Dec-2015
Category:
Upload: hugo-hodges
View: 233 times
Download: 0 times
Share this document with a friend
57
Programming Paradigms for Concurrency Lecture 10 Part III – Message Passing Concurrency
Transcript

Programming Paradigms for Concurrency

Lecture 10

Part III – Message Passing Concurrency

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

Simple Example: Reference Cells

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 Synchronous Events

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• ...

Implementation of First-Class Events

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!


Recommended