COP 4020 Lecture -*- Outline -*- * Message-Passing Concurrency (Ch 5) Based on Peter van Roy and Seif Haridi's book, "Concepts, Techniques, and Models of Computer Programming" (MIT Press, 2004), where all references that are not otherwise attributed are found. ** motivation ------------------------------------------ MESSAGE-PASSING CONCURRENCY (5) def: message passing is a programming style in which a program consists of independent entities (agents) that interact by sending messages asynchronously Importance: ------------------------------------------ ... service-oriented architecture multi-agent systems client-server systems (*key*) distributed systems (in general, if don't use shared memory) good for building highly reliable systems (as in Erlang) Q: What is asynchronous? Not waiting for a reply Q: What's the importance of message passing? - multi-agent systems (same as message passing systems) - natural for distributed systems reflects structure of system and its costs - lends itself to building highly reliable systems failures don't make other entities hang ** concepts ------------------------------------------ CONCEPTS new idea: asynchronous communication channel - named - all clients can always send - server can read all messages Fundamentally nondeterministic Port: channel with an associated stream send = append to stream read = take head from stream ------------------------------------------ Q: What does it mean to be "named"? several clients can use it by using the name Q: Do we still have referential transparency? ** semantics by example See NewPortSemantics.oz to run these examples. Don't feed the following buffer as a whole! Instead feed it one contiguous region at a time. ------------------------------------------ SEMANTICS BY EXAMPLE % Basic semantics of NewPort and Send declare Strm Port in {Browse Port} {Browse Strm} % NewPort takes an variable representing a stream % and initializes the Port argument {NewPort Strm Port} % Sending on the port adds to the stream thread {Delay 1000} {Send Port 3} end thread {Delay 999} {Send Port 4} end thread {Delay 3001} {Send Port 5} end thread {Delay 3000} {Send Port 6} end ------------------------------------------ See NewPortSemantics.oz shows 4|3|6|5|_ the _ is a read-only undetermined var Send does 2 things: 1. puts the given value at the end of the stream 2. associates the port with a fresh end of stream store variable Q: How would you do this in Java? A port is a new kind of object. Use object identity of the port as its name. Send is an operation on ports that mutates the stream it contains. A read-only stream interface is used to access the stream by clients. Q: Why is a mutation needed to describe the meaning of Send? Because the different threads all share the same port, but don't pass it around explicitly. ------------------------------------------ NEWPORT AS A FUNCTION declare S2 P2 U1 U2 in P2 = {NewPort S2} % You can send anything to a port {Browse S2} {Delay 3000} thread {Send P2 7} end thread {Send P2 unit} end thread {Send P2 true} end % You can send undetermined store variables thread {Send P2 U1} end % You can send partial values {Send P2 hmmm(x:U2)} thread U1 = 4020 end ------------------------------------------ See NewPortSemantics.oz shows hmmm(x:U2)|7|unit|true|4020|_ ------------------------------------------ ALL SENDS TO A PORT ARE MERGED declare P S {Browse S} P={NewPort S} thread for I in 1..10 do {Send P I} {Delay 500} end end thread for J in 201..210 do {Send P J} {Delay 300} end end ------------------------------------------ See NewPortMerging.oz This shows that the sends all mix together, which is essential for servers ------------------------------------------ THE STREAM IS ONLY READABLE AFTER declare S3 P3 in P3 = {NewPort S3} {Browse S3} local Z in S3 = 1|Z end % suspends {Browse ok} ------------------------------------------ You can't bind a stream directly after giving it to NewPort It's put in the read-only store, see section 3.7.5 ------------------------------------------ NO SHARING OF STREAMS BETWEEN PORTS declare S4 P4a P4b in P4a = {NewPort S4} {Browse gotHere} P4b = {NewPort S4} % suspends {Browse P4a == P4b} ------------------------------------------ More in NewPortSemantics.oz Similarly, you can't reuse a stream once passed to NewPort As the second use will suspend... ------------------------------------------ MUST GIVE NEWPORT AN UNDETERMINED STORE VARIABLE declare S5 S5a P5 in {Browse S5} {Browse S5a} S5=5|S5a {Browse 'calling NewPort with S5...'} P5 = {NewPort S5} % suspends {Browse pastIt} ------------------------------------------ More in NewPortSemantics.oz ** operational semantics (skip formal semantics details) ------------------------------------------ SYNTAX Syntax: ::= ... | {NewPort } | {Send } {NewPort S P} creates port named P with stream S {Send P X} appends X to stream corresponding to P ------------------------------------------ NewPort can be seen as a function that takes a stream and returns a port: P = {NewPort S} Q: Why can't we write this in the declarative model? no, that would violate determinism of the declarative model Note: could try using streams to be ports... fun {NewPortNot S} fun {Send X} S1 in S = X|S1 S1 end end But then the result of {NewPortNot S} can't be used by 2 clients simultaneously, as only one can access the stream at a given time. ------------------------------------------ MUTABLE STORE m in MutableStore = Variable -> Variable Operations: {}: MutableStore update: MutableStore -> Variable x Variable -> MutableStore lookup: MutableStore x Variable -> Variable lookup(update(m)(y,x), z) = if z == y then x else lookup(m,z) Notation: write x:y for a binding in MutableStore typical m is {p1:s1, p2:s2} update({p1:s1})(p2, s2) = {p1:s1, p2:s2} lookup({p1:s1, p2:s2}, p1) = s1 ------------------------------------------ ------------------------------------------ SEMANTICS Sequential (-d->) configurations: (ST,s,m) in State = Stack x Store x MutableStore + Message Message = String Stack = ( x Environment)* Store = Variable -> Value T = Message + { (nil,s,m) | s in Store, m in MutableStore} [NewPort call] (({NewPort X Y},E)|Rest, s, m) -d-> (Rest, s', m') where undetermined(s,E(Y)) and n is a port name and n not in range(s) and s' = bind(s)(s(E(Y)),n) and m' = update(m)(E(Y), E(X)) [Send call] (({Send X Y},E)|Rest, s, m) -d-> (Rest, s'', m') where determined(s,E(X)) and s(E(X)) is a port name and lookup(m, E(X)) = z and z'#s' = alloc(s) and m' = update(m)(E(X), z') and l = '|'(E(Y) z') and s'' = bind(s')(s'(z), l) ------------------------------------------ For what Send is doing, see also StreamEnd.oz recall that Store is more complex if we also want ByNeed triggers Q: What should happen if we do {NewPort S P} and P is already determined? raise an error condition (work that out?) Q: What should happen if we do {Send P Y} and P is not a port? raise an error condition Q: What should happen if we do {Send P Y} and P is not determined? suspend Q: What should happen if we do {Send P Y} and Y is not determined? doesn't matter, just keep going Q: What should happen in [Send call] if m(E(X)) is not defined? That can't happen, port names are always associated with determined store variables. This is simplified, in that the end of the stream is supposed to be a read-only view, not just a variable. Q: Does this affect garbage collection? Yes, - y is reachable if m contains x:y and x is reachable - if x is unreachable, and m contains x:y, then remove x:y from m.