Com S 541 Lecture -*- Outline -*- * The Data-Driven Concurrent 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. ** motivation Q: Why is declarative concurrency useful? Concurrency is: - needed for programs that interact with their environment GUIs, agents, OS Interaction - Lets you program be organized into parts that are independent Client/server, producer/consumer, pipelines ** advantages ------------------------------------------ DECLARATIVE CONCURRENCY (4) Extension of declarative model Advantages: - preserves declarative reasoning - only changes performance - results calculated incrementally ------------------------------------------ Q: why is incremental calculation of results useful? It supports interaction ** the data-driven concurrent model (section 4.1) ------------------------------------------ DATA-DRIVEN CONCURRENT MODEL (4.1) ::= ... | thread end | {ByNeed } "by need trigger" New expression sugar: thread end ==> local Z in thread Z= end Z end ------------------------------------------ Q: What does a thread do? Executes its body can currently Q: Why is this kind of concurrency simple to use? Programs have no observable non-determinism as long as there are no binding failures. There are no race conditions in the declarative concurrent model. *** 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 E.g., local X in thread {Browse X} end thread 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 A=3 end thread B=A+2 end end ------------------------------------------ ... in all possible executions a step that binds a dataflow variable X always occurs before a step that needs X. 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 visible to the programmer. ------------------------------------------ ... 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 would we formalize the semantics of threads? Two approaches: true parallelism or interleaving for interleaving: track sets or let it be nondeterministic We'll show an interleaving semantics that allows for nondeterminism *** Operational semantics ------------------------------------------ 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 declartive model. Let Td be terminal configurations of the declartive model. (ST,s) -d-> (ST', s') [run] ----------------------------------- ({ST} + MST, s) --> ({ST'} + MST, s') (ST,s) -d-> Msg [fail] ------------------------- ({ST} + MST, s) --> Msg [thread] ({(thread S end,E)|Rest} + MST, s) --> ({(S,E)|nil} + {Rest} + MST, s) [deallocate] ({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] 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) ------------------------------------------ EXAMPLE local Answer in local B in thread B = true end if B then Answer=yes else Answer=no end end end ------------------------------------------ ** what is declarative concurrency (4.1.4) Q: What are the differences between the data-driven concurrent model and the strict functional model? - Inputs and outputs might contain unbound variables - Execution might not terminate because the input can be indefinitely long streams *** partial termination ------------------------------------------ PARTIAL TERMINATION def: A function *partially terminates* if all threads are blocked and further binding of inputs would cause more compuatation. EXAMPLE fun {Double Xs} case Xs of X|Xr then 2*X | {Double Xr} end end ------------------------------------------ Q: What happens if Xs 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 to be a function? that for equal inputs, get the same output 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. ... { 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 Q: How would you use the notion of logical equivalence to define when a function is declarative in the concurrent model? The book's answer: def: a function is *declarative* iff all exececutions with a given set of inputs either: (1) all do not terminate, or (2) they all eventually reach partial termination and give results that are logically equivalent This is really modulo an equivalance 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 propogate 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 propogate out of threads? Make a race condition: fun {Random } try thread raise 1 end end thread raise 0 end end catch X then X end end Why doesn't this work without exceptions that propogate out of threads? get failure of whole program, so no same "answer"