CS 330 Home Schedule Resources

Homework 10
Threads, declarative concurrency and nondeterminism

Estimated time: 3 hours

Objective: familiarize yourself with using threads in programs. First we will study how to use threads with dataflow variables. Then we will examine observable nondeterminism.

Exercises

  1. Threads. (3 points)
    Consider the two following program fragments:

    declare 
    X Y Z in
    thread if X==1 then Y=2 else Z=2 end end
    thread if Y==1 then X=1 else Z=2 end end
    
    declare
    X Y Z in
    thread if X==1 then Y=2 else Z=2 end end
    thread if Y==1 then X=1 else Z=2 end end
    X=2
    
    Indicate the values of the variables X, Y, and Z at the end of execution. (You may check your guess using the Browser.)

  2. The Wait operation. (5 points) Explain why the {Wait X} operation, to wait for a variable to become bound, could be defined as:
    proc {Wait X}
      if X==unit then skip else skip end
    end
    
    Use your understanding of the == primitive and the semantics of the if statement in your explanation.

  3. Order-determining concurrency. (10 points) Explain what happens when executing the following:
    declare A B C D in
    thread D=C+1 end
    thread C=B+1 end
    thread A=1 end
    thread B=A+1 end
    {Browse D}
    
    In what order are the threads created? In what order are the additions done? What is the output? How many possible orders of execution are there? Also, compare with the following:
    declare A B C D in
    A=1 
    B=A+1
    C= B+1
    D=C+1
    {Browse D}
    
    Here there is only one thead. In what order are the additions done? What is the output? How many possible execution orders are there? What difference, if any, is there between these two code fragments?

  4. Explicit State and Concurrency (7 points) Use the function CTest below to examine the effects of explicit state on concurrency. The cell C behaves like the variables you are familiar with in C++ and Java. Unlike a dataflow variable, which can be bound to only one value, a cell starts off with an initial value that can be changed. @C means "the current value of C" and C := X means "assign C to be equal to X". Thus C := @C + 1 increments C by one. What value should CTest return?
    declare
    fun {CTest}
    C={NewCell 0}
    Idone Jdone in
    thread % thread T1
    for N in 1..10000 do % increment C 10000 times
    C:= @C + 1
    Idone=true
    end
    end
    thread % thread T2
    for N in 1..10000 do % increment C 10000 times
    C:= @C + 1
    Jdone=true
    end
    end
    {Wait Idone}{Wait Jdone}
    @C
    end

    for X in 1..10 do % run CTest 10 times and display the result
    Res = {CTest} in
    {Show [' Result is ' Res]}
    end
    {Show done}

    Notice that even though CTest is called with the same arguments each time (i.e., no arguments), it returns different results. This is an example of observable nondeterminism.

    It takes a certain amount of time to perform an addition "@C + 1". Suppose that in between the time that thread T1 reads @C and the time it writes the new value @C + 1 back into C, thread T2 also reads @C. What would be the result? How does this help explain the output of the program?

  5. Thread semantics. (10 points) Consider the following variation of the statement used in section 4.1.3 to illustrate thread semantics:
    local B in
      thread B=true end          % (1)
      thread B=false end         % (2)
      if B then                  % (3)
         {Browse yes}            % (4)
      end
    end
    
    For this exercise, do the following:
    1. Enumerate all possible executions of this statement. Write your answers in the form (2 1 3) to mean that line 2 executes, then line 1, then line 3.
    2. Some of these executions cause the program to terminate abnormally. Make a small change to the program to avoid these abnormal terminations. Hint: Bind B to the same value both times. Or, catch the exception instead.

  6. Concurrent Fibonacci. (10 points) Consider the following sequential definition of the Fibonacci function:
    fun {Fib X}
    if X=<2 then 1
    else {Fib X-1} + {Fib X-2} end
    end
    
    and compare it with the concurrent definition given in section 4.2.3. Run both on the Mozart system to compare their performance.

  7. Dataflow behavior in a concurrent setting. (15 points) Consider the function {Filter In F}, which returns the elements of In for which the boolean function F returns true. Here is a possible definition of Filter:
    fun{Filter In F}
      case In
        of X|In2 then
          if{F X} then X|{Filter In2 F}
          else {Filter In2 F} end
        else
          nil
      end
    end
    
    Executing the following:
    {Show {Filter [5 4 1 4 0] fun{$ X} X > 2 end}}
    displays
    [5 4]
    (Note: unlike Browse, Show outputs to the emulator window and does not update itself if an unbound variable becomes bound.) So Filter works as expected in the case of a sequential execution when all the input values are available. Let us now explore the dataflow behavior of Filter.

  8. Basics of laziness. (5 points) Consider the following program fragment:
    fun lazy {Three} {Delay 1000} 3 end
    Calculating {Three} + 0 returns 3 after a 1000 ms delay. This is as expected, since the addition needs the result of {Three}. Now calculate {Three}+0 three times in succession. Each calculation waits 1000 ms. "But wait!" cries Bob. "Three is supposed to be lazy, so its result should be calculated only once!" Explain to Bob why this happens.

  9. Laziness and concurrency. (10 points) This exercise looks closer at the concurrent behavior of lazy execution. Execute the following:
    declare
    fun lazy {MakeX} {Browse x} {Delay 3000} 1 end
    fun lazy {MakeY} {Browse y} {Delay 6000} 2 end
    fun lazy {MakeZ} {Browse z} {Delay 9000} 3 end
    
    X={MakeX}
    Y={MakeY}
    Z={MakeZ}
    
    {Browse (X+Y)+Z}
    
    This displays x and y immediately, z after six seconds, and the result 6 after fifteen seconds. Explain this behavior. What happens if (X+Y)+Z is replaced by X+(Y+Z) or by thread X+Y end + Z? Which form gives the final result the quickest? How would you program the addition of n integers i1,...,in, given that integer ij only appears after Tj ms, so that the final result appears the quickest?

  10. Laziness and incrementality. (5 points) Let us compare the kind of incrementality we get from laziness and from concurrency. Section 4.3.1 gives a producer/consumer example using concurrency. Section 4.5.3 gives the same producer/consumer example using laziness. In both cases, it is possible for the output stream to appear incrementally. What is the difference? What happens if you use both concurrency and laziness in the producer/consumer example?

  11. Limitations of declarative concurrency. (10 points) Section 4.8 states that declarative concurrency cannot model client/server applications, because the server cannot read commands from more than one client. Yet, the declarative Merge function of scetion 4.5.6 reads from three input streams to generate one output stream. How can this be?

  12. Concurrency for free. (10 points) Run both the oz code and the java code given at the following link. Report the execution times for creating 1000, 10000, and 100000 threads that each yield 10 times (if possible). Also report the characteristics of the machine you ran it on (CPU speed, RAM).

  13. Feedback.
    Scale
    1. Not at all
    2. Not very
    3. Slightly
    4. Somewhat
    5. Mostly
    6. Very
    7. Extremely