COMP2310/COMP6310:
Concurrent and Distributed Systems
Semester 2 2012

Tutorial / Laboratory 1

Introduction to FSP Processes and Concurrent Execution

Objectives

Preparation

Before your session, please:

Tutorial Exercises

Please write your answers on paper (if you have none, then use a text editor of your choice). Do not use the LTSA tool yet - just concentrate on the ideas for the moment; don't get bogged down in the details for the syntax. Also proper instructions on how to bring up and use the tool will come in the Lab Exercises.

  1. (Ex 2.1 Magee & Kramer) For each of the following processes, give a Finite State Process (FSP) description of the following Labelled Transition System graphs. Note that the numbering of the states (except initial state 0) is not significant.
    1. MEETING
    2. DOUBLE
    3. PERSON

  2. (Ex 2.2 Magee & Kramer) A variable stores values in the range 0..N and supports the actions read[i] and write[j], where i is the variable's current value and j will become the variable's new value. Model the variable as the FSP process VARIABLE. For N=2, check that it can perform the actions given by the trace:

  3. Draw the resulting state machine (Labelled Transition System) diagram for VARIABLE (for brevity, abbreviate read[0] for r[0], write[0] for w[0] etc).

  4. Write a version of the variable process above whose value can only be changed by at most one from its current value. Use the when construct and call the FSP process INC_VARIABLE.

  5. (Ex 2.4 Magee & Kramer) A sensor measures the water level of a tank. The level (initially 5) can vary between 0 and 9. The sensor outputs a low signal if the level is less than 2 and a high signal if the level is greater than 8, otherwise it outputs normal. Model the sensor as an FSP process SENSOR. (Hint: the alphabet of SENSOR is {level[0..9], low, high, normal}. The event level[i], where 0 <= i <= 9, means that the level has changed to have value i; assume that this can occur at any time).

Laboratory Exercises

You will first need to customize your command line environment for this course. To do this, simply add to your ~/.bashrc file the line: Make sure the line is properly terminated (press the `Enter' key at the end of the line -- otherwise it won't work!). To ensure that this file always gets sourced when you log in, add to your ~/.profile file the line:
  1. Execute the command ltsatool & to bring up the LTSA analyser. (this is in fact an `alias' command; type the command alias to see how it is implemented).

    Type in your MEETING program of Tutorial Question 1 into the Edit pane and select Build -> Compile and check that the resulting LTS is correct in the Draw pane. If you get a syntax error Build -> Parse can be used to indicate the point in the code where the error was detected.

  2. Repeat this for VARIABLE program. Using Check -> Run, generate the trace from Tutorial Question 2. Activate Window -> Alphabet and re-compile. In the Alphabet pane, confirm that the Alphabet of VARIABLE is what you expect. Again run the program, but select the Draw pane and observe the transitions in the LTS.

    Note: write.2 is the old FSP format for indexed events; it is the same as write[2]. The Animator still uses the old format.

  3. A definition of the INC_VARIABLE process for N=2 which does not use the when construct is as follows:
       INC_VARIABLE_ = INC_VARIABLE_[0],
       INC_VARIABLE_[0] = ( read[0] -> INC_VARIABLE_[0] 
                          | write[0] -> INC_VARIABLE_[0]
                          | write[1] -> INC_VARIABLE_[1] ),
       INC_VARIABLE_[1] = ( read[1] -> INC_VARIABLE_[1] 
                          | write[1] -> INC_VARIABLE_[1]
                          | write[0] -> INC_VARIABLE_[0] 
                          | write[2] -> INC_VARIABLE_[2] ),
       INC_VARIABLE_[2] = ( read[2] -> INC_VARIABLE_[2]
                          | write[2] -> INC_VARIABLE_[2]
                          | write[1] -> INC_VARIABLE_[1] ).
    
    Type in your INC_VARIABLE program from the tutorial and compile it. Add the INC_VARIABLE_ code above to the Edit pane and re-compile. Use the tool to verify that the two programs have equivalent LTS graphs.

  4. Download and compile the file Race.java. The usage of the program is: in which p threads will try to increment a (shared) variable n times. Inspect the code. Locate the parts of the program where the threads get created and started. Also inspect the run() method that each thread executes. Don't worry if you don't understand the API at this point - we will cover that later.

    The program aims to demonstrate interference when each thread tries to increment a shared counter ctr - if there was no interference, the final value of the counter would be n. You will notice that the main program waits until all threads have entered their run method (and incremented the threadsReady shared variable) - and the threads in turn wait until the main program `signals' it has reached this point (by setting the mainReady shared variable - all this is to maximize the amount of interference!

    1. Run the program repeatedly with say n=100000 and p=1. There should be no surprises: the final values of the counter should be 100000. Now repeat this with p=2,3,4. What trends do you observe for the runs showing the maximum interference?

    2. In your copy of Race.java, uncomment the call to Thread.yield() in the run() method. Recompile and repeat the above experiments. What do you observe? Why might it be different from the above case?

    3. Now comment out the keyword volatile in the definition of threadsReady and mainReady. This keyword instructs the compiler that the variable may be modified at any time by other threads, forcing the corresponding memory location to be accessed every time the variable is (otherwise the compiler might try optimizations such as accessing only once the memory location of an variable which is not changed by the loop). Re-compile and run with 4 and 8 threads. You should (sometimes) observe a deadlock. Where and how does this occur? (ask your tutor for an explanation if need be).

  5. Download and compile the file race.c. The usage of the program is: in which p threads scheduled on c cores (processors) will try to increment a (shared) variable n times. Inspect the code. Again locate the parts of the program where the threads get created and started. Also inspect the unsafeSum() function that each thread executes. You will note that the API is considerably more complex - this represents a trade-off of simplicity for greater control. The program is like the Java version but also uses thread affinity to instruct the operating system to only schedule a thread on a specific core. Again don't worry if you don't understand the API at this point - we will cover that after the break.

    1. Execute the command x86info. This will display the number of cores available on your system (among other things). On the machines in the CSIT labs, the machines should have 4 cores. Note: the interference behaviour of this and the previous program is highly system dependent. You may observe other trends on different machines. In particular, it behaves differently on the sever partch .

    2. Generate the assembly language code for the program with the command
        gcc -Wall -O -S race.c
      and inspect the resulting file race.s. Search for unsafeInc:, the entry point point of the function that increments the counter. You will observe that the body of the function loads the variable (the address of which is in register %rdi) into register %rax, increments this register, and stores the incremented value back into the variable.

      Now compile the program with the command

        gcc -Wall -O -o race race.c -lpthread
      Run the program with one thread, e.g. ./race 100000 1, and now vary the number of threads from 1 to 4. Here, all threads run on a single core. What do you observe this time? Try a much longer run, e.g. ./race 100000000 4; do you observe any interference? Why might short runs see no interference when longer ones do? (Hint: on sufficiently short runs, the threads can complete within a single timeslice),

    3. Finally run the program in parallel, that is with threads running on multiple cores (say c = p = 2,3,4), e.g.
        ./race 100000 2 2
      How much interference do you get now? Comparing these results with that of the Java program, would you expect that the Java threads are running on one core or on several?

Homework

  1. Repeat Laboratory Exercise 1 for the DOUBLE and PERSON programs.

  2. Load and run the DRINKS process from Chapter 2 of the text. Observe the transitions in the Draw pane as you run the process. Repeat this for FAULTY and THREAD examples (and any other that you wish to improve your understanding of). The FSP examples form the textbook are located in the directory /dept/dcs/comp2310/public/ltsa_examples.

  3. (Ex 2.5 Magee & Kramer) A drinks dispensing machine charges 15p for a can of Sugarola. The machine accepts coins with denominations 5p, 10p and 20p, and gives change. Model the machine as an FSP process DRINKS.

Challenge Exercise!

(for the Java hackers) Is it possible to implement thread affinity in Java programs, as was done for the C program race.c? I made a quick internet search last month which suggested this would not be easy - indeed one article stated that it goes against the philosophy of java threads. However, maybe it is possible - kudos to anyone who can make progress on this.