COP 4020 Lecture -*- Outline -*- * The Data-Driven Concurrent (really parallel) Model (Ch 4) 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. Really, this is parallelism, not concurrency. The goal is to get answers faster (in terms of clock time) ** motivation ------------------------------------------ WHY IS CONCURRENCY USEFUL? ------------------------------------------ ... - Get answers faster (e.g., weather predictions) e.g., NOAA weather supercomputer needs to do 1.3 Trillion ops/sec to get a 6 day in advance forecast - needed for programs that interact with their environment GUIs, agents, OS Interaction Why? to overlap computations with user time - Lets your program be organized into parts that are independent Client/server, producer/consumer, pipelines How does that help? each part concentrates on a smaller job ** advantages ------------------------------------------ DECLARATIVE CONCURRENCY (4) Extension of declarative model - threads - by need triggers (lazy evaluation) Advantages: - preserves declarative reasoning - only changes performance - results calculated incrementally - can improve thruput (answers faster) Why is incremental calculation of results useful? ------------------------------------------ ... It supports interaction (e.g., with users) Also performance (e.g., better thruput) ** the data-driven concurrent model (section 4.1) ------------------------------------------ DATA-DRIVEN CONCURRENT MODEL (4.1) ::= ... | thread end "thread execution" New expression sugars: thread end ==> local Z in thread Z= end Z end ------------------------------------------ Q: What does a thread do? Executes its body concurrently with the following statements ------------------------------------------ IN OTHER LANGUAGES How would you create new threads in Java? In C++? C? ------------------------------------------ ... In Java you make a Thread object with a run method that does the statements you want, then you call the run method. (* See Queue.java and QueueTest.java *) In C++ or C you use a library, which does something similar. (like PThreads) Q: What kind of mechanisms are used in Java to deal with mutable state and concurrency? monitors (realized by classes with synchronized methods) locks, etc. Q: What's the difference between running a thread and a process? Several threads will share the same store, but each process has its own store (memory). ------------------------------------------ BENEFITS OF DECLARATIVE CONCURRENCY Why is declarative concurrency, without mutable state (cells), simple to use? ------------------------------------------ ... Programs have no observable non-determinism as long as there are no binding failures (or other exceptions). There are no race conditions in the declarative concurrent model (assuming there are no exceptions due to binding failures) *** Basic concepts (4.1.1) ------------------------------------------ BASIC CONCEPTS (4.1.1) Interleaving: Thread 1 --> --> --> --> Thread 2 --> --> --> --> def: Steps of a computation follow the *causal order* iff ------------------------------------------ ... in all possible executions a step that determines the value of a dataflow variable X always occurs before a step that needs X. ------------------------------------------ EXAMPLES local X in thread {Browse X+1} end thread {Delay 5000} X=3 end end FOR YOU TO DO What order of steps follow the causal order? local A B in thread {Browse A} end thread {Browse B} end thread {Delay 5000} A=3 end thread B=A+2 end end ------------------------------------------ See BrowseTest.oz Q: How might the interleaving semantics be implemented? On a computer with parallel processors in parallel ------------------------------------------ NONDETERMINISM def: An execution is *nondeterministic* iff def: *observable nondeterminism* is nondeterminism that can be seen in some output. ------------------------------------------ ... there is a choice of next states in some configuration. Q: How would we formalize "visible to the programmer"? Formalize the notion of observation, e.g., exists a program that gives different outputs In the declarative model with concurrency, nondeterminism is hidden if - there are no unification failures, and - no exceptions Q: What states can a thread be in? running, ready, blocked Q: What is fairness in scheduling? A ready thread eventually executes no starvation ** Semantics of threads (4.1.2) Q: How could we formalize the semantics of threads? Two approaches: true parallelism or interleaving for interleaving: track sets or let it be nondeterministic Oz uses an interleaving semantics that allows for nondeterminism Why? it can be implemented on uniprocessors (the normal implementation is just interleaving) *** Operational semantics (skip, or go over quickly in favor of examples) ------------------------------------------ CONFIGURATIONS (MST,s) in State = MultiSet(Stack) x Store + Message Stack = ( x Environment)* T = Message + { (MST,s) | s in Store, each ST in MST is nil} Message = String input[[S]] = ({[(S,{})]}, {}) output((MST,s)) = s output(Msg) = Msg ------------------------------------------ Q: When is a semantic stack runnable in a store s? When there is a transition (in the old system) from the state formed from that static and s ------------------------------------------ TRANSITIONS (-->) Let -d-> be the transitions of the declarative model. Let Td be terminal configurations of the declarative model. (ST,s) -d-> (ST', s') [run] ----------------------------------- ({ST} + MST, s) --> ({ST'} + MST, s') (ST,s) -d-> Msg [fail] ------------------------- ({ST} + MST, s) --> Msg [thread-creation] ({(thread S end,E)|Rest} + MST, s) --> ({(S,E)|nil} + {Rest} + MST, s) [thread-finish] ({nil} + MST, s) --> (MST, s) ------------------------------------------ Explain the notation in the run rule Q: In the run rule, do we need a side condition saying the stack is runnable in the store? No, that's the hypothesis in the rule Q: Could be [thread-creation] be a -d-> rule? no Q: According to the book "A blocked semantic stack can be reclaimed if its activation condition depends on an unreachable variable"; how would you formalize that? [reclaim] ({ST} + MST, s) --> (MST, s) where blocked(ST,s) and unreachable(blockedVar(ST,s),MST) Then we have to define what these predicates are... Q: What's the [fail] rule for? When a thread doesn't catch an exception. *** example (4.1.3) the following code is in ModelTests.oz ------------------------------------------ EXAMPLES What is the final value of Answer? Why? % (a) local Answer in local B in thread B = true end if B then Answer=yes else Answer=no end end {Browse Answer} end % (b) local Answer in local B in thread if B then Answer=yes else Answer=no end end thread B = true end end {Browse Answer} end % (c) local Answer in local N in thread N=0 end thread N=11 end thread N=222 end thread N=3333 end Answer=N end {Browse Answer} end % (d) local Answer Square in fun {Square I} thread I*I end end Answer={Square 9} {Browse Answer} end ------------------------------------------ in ModelTests.oz ... (a) yes (b) yes (only 1 possible value, due to dataflow) (c) this has no value (unification failure) (d) 81 (try desugaring it) (Can look at the semantics, if doing that, to justify) ** what is declarative concurrency (4.1.4) Q: What does it mean to be a declarative function? that for equal inputs, get the same output Q: What problems might make it hard to define "referential transparency" or "declarative" in the data-driven concurrent model? It is harder to define than in the strict functional model because: - Interactions with active agents may make it impossible to tell when the program is terminated (e.g., indefinitely long streams) - "the same value" may not be clear due to unbound variables (non-strict) *** partial termination ------------------------------------------ PARTIAL TERMINATION def: A function *partially terminates* if all threads are blocked and further binding of inputs would cause more computation. EXAMPLE fun {Double X|Xr} 2*X | thread {Double Xr} end end ------------------------------------------ See Double.oz, DoubleDesugared.oz, DoubleTest.oz Note: no nil case, because this is a stream example Q: What happens if Xr grows in the example? % The following creates a lot of threads... local Xs Ys in thread Ys={Double Xs} end thread Xs=1|Ys end {Browse Ys} end *** logical equivalence Q: What does it mean for two partial values to be equivalent? Q: E.g., is 1|2|3|Xr equivalent to 1|2|3|Yr ? ------------------------------------------ EQUIVALENT STORES? % Example 1 local X Y in X=1 Y=X end % vs. local X Y in Y=X X=1 end % Example 2 local X Y Z in X=foo(Y W) Y=Z end % vs. local X Y Z in X=foo(Z W) Y=Z end ------------------------------------------ Q: Are the stores produced the same? Q: If so, what changes could you make to produce a difference? ------------------------------------------ CONSTRAINTS ON THE STORE def: a *constraint* is def: Let c be a constraint. Then values(x,c) is ------------------------------------------ ... a member of the set Constraint = Store -> Bool i.e., a predicate over store variables (i.e., an observation) ... { v | s' is a store, s=bind(s')(x,v), c(s) }, the set of possible values that the store variable x can take on when c holds Q: What is values(x, x=foo(y w) /\ y=z /\ y=1 /\ w=3)? Q: What is values(x, x=foo(z w) /\ y=z /\ y=1 /\ w=3)? *** declarative concurrency ------------------------------------------ DEFINING "DECLARATIVE" Q: How would you use the notion of logical equivalence to define when a function is declarative? ------------------------------------------ ... The book's answer: def: a function is *declarative* iff all executions with a given set of inputs either: (1) all do not terminate, or (2) they all eventually reach partial termination and results (and stores) that are logically equivalent This is really modulo an equivalence relation on store variables in the different executions. Q: Is it possible to observe different outputs from a declarative program? no Why not? How does logical equivalence correspond to observable equivalence? Q: How would you prove that the data-driven concurrent model is declarative? Induction on structure of programs, what is the case for threads? *** failure Q: Are failures declarative? Yes, they are all or nothing local X Y in thread X=1 end thread Y=2 end thread X=Y end end How can we tell if this follows our semantics exactly? Q: What would happen if we just let the exception propagate out of the threads? we could see where it came from Q: Can you write a program that would be non-declarative by using exceptions that propagate out of threads? Make a race condition: fun {RandomWithExceptions} local X in thread try X=1 catch _ then skip end end thread try X=0 catch _ then skip end end X end end Notice that this avoids propogating exceptions out of threads, since exception handling only works within a single thread. Why doesn't this work without exceptions that propagate out of threads? get failure of whole program, so no same "answer"