CS 334
Programming Languages
Spring 2002

Solution to Assignment 10
Due Thursday, 5/9/2002


  1. Do problem 14-22 on page 14.38 of the text.

    Solution: This is not equivalent. Suppose that the semaphore is initialized with value of 1. Then three processes execute Wait(S). The first succeeds, reducing S to 0, while the other two fail, but also reduces S so it has value -2. Now if the first process executes Signal(S), then S will be increased to -1, but because S is less than 0, no other process will be awakened. Thus this solution is not correct.

  2. Do problem 14-26, parts a and b only, on page 14.39 of the text.

    Solution sketch: Suppose entry m of a monitor calls entry n. If process p calls m and succeeds then it will stall when called at n because there is already a process (p, itself) in the monitor. Hence deadlock results.

    This is not a problem in Java, because if process p grabs the lock for the object to execute synchronized method m, and m call synchronized n, then p still has the lock when calling n, so has permission to call n. the only tricky part is that the lock must be released only by the method that originally granted the lock.

  3. Do problem 14-29 on page 14.39 of the text.

    Solution: If the "synchronized" is dropped from method "put" then two processes might enter put simultaneously. If size = buffer.length-1, then if two processes get in before either increments end, then both will add items to the buffer when there is only room for one.

  4. Do problem 14-36 on page 14.40 of the text.

    Solution: Implementing the count associated with the Semaphore after each Signal entry is correct, because it is only incremented after the Signal has been accepted and the resource is always returned to the pool rather than being handed directly to a waiting process as in the primitive definition of semaphore.

  5. The ML function, primesto n, given below, can be used to calculate the list of all primes less than or equal to n by using the classic "Sieve of Eratosthenes".

    (* Sieve of Eratosthenes: Remove all multiples of first element from list,
       then repeat sieving with the tail of the list.  If start with list [2..n]
       then will find all primes from 2 up to and including n. *)
    fun sieve [] = []
      | sieve (fst::rest) = let
           fun filter p [] = []
             | filter p (h::tail) = if (h mod p) = 0 then filter p tail
                                                else h::(filter p tail);
           val nurest = filter fst rest
         in
            fst::(sieve nurest)
         end;
    
    (* returns list of integers from i to j *)
    fun fromto i j = if j < i then [] else i::(fromto (i+1) j);
    
    (* return list of primes from 2 to n *)
    fun primesto n = sieve(fromto 2 n);
        

    Notice that each time through the sieve we first filter all of the multiples of the first element from the tail of the list, and then perform the sieve on the reduced tail. In ML, one must wait until the entire tail has been filtered before you can start the sieve on the resulting list. However, one could use parallelism to have one process start sieving the result before the entire tail had been completely filtered by the original process.

    Here is a good way to think of this concurrent algorithm which will use a slightly modifed version of the Java Buffer class defined in the lecture notes. The only modifications necessary are to have the buf instance variable hold an array of int, rather than char, and then change the appropriate types of parameters, return types, and local variables in the put and get methods from char to int.

    The main program should begin by creating a Buffer object (say with 5 slots) and then should successively put the numbers from 2 to n (for some fixed n) into the Buffer using the put method, and finally put in -1 to signal that it is the last element. After the creation of the Buffer object, but before starting to put the numbers into the Buffer, the program should create a Sieve object (using the Sieve class described below) and pass it the Buffer object (as a parameter to Sieve's constructor). The Sieve object should then begin running in a separate thread while the main program inserts the numbers in the buffer.

    After the Sieve object has been constructed and the Buffer object stored in an instance variable, in, its run method should get the first item from in using the get method. If that number is negative then the run method should terminate. Otherwise it should print out the number (System.out.println is fine) and then create a new Buffer object, out. A new Sieve should be created with Buffer out and started running in a new thread. Meanwhile the current Sieve object should start filtering the elements from the in buffer. That is, the run method should successively grab numbers from the in buffer, checking to see if each is divisible by the first number that was obtained from in. If a number is divisible, then it is discarded, otherwise it is put on buffer out. This reading and filtering continues until a negative number is read. When the negative number is read then it is put into the out buffer and then the run method terminates.

    If all of this works successfully then the program will eventually have created a total of p + 1 objects of class Sieve (all running in separate threads), where p is the number of primes between 2 and n. The instances of Sieve will be working in a pipeline, using the buffers to pass numbers from one Sieve object to the next.

    Please write this program in Java using the Buffer class in Lecture 23, modified as suggested above so that it holds ints rather than chars. Each of the buffers used should be able to hold at most 5 items.

    Truth in Advertising: Because these threads will all be running on a single-processor machine, the program will ultimatively run much, much slower than a sequential program doing the same task!

    Solution: The following is Brent Yorgey's solution to this problem.

    import java.io.*;
    
    class Buffer {
       private final int[] buf;
       private int start = -1;
       private int end = -1;
       private int size = 0;
        
       public Buffer(int length) {
          buf = new int[length];
       }
    
       public boolean more() {
          return size > 0;
       }
    
       public synchronized void put (int i) {
          try {
             while (size == buf.length) wait();
             end = (end + 1) % buf.length;
             buf[end] = i;
             size++;
             notifyAll();
          } catch (InterruptedException e) {
             Thread.currentThread().interrupt(); 
          }
       }
    
       public synchronized int get() {
          try {
             while (size == 0) wait();
             start = (start+1) % buf.length;
             int i = buf[start];
             size--;
             notifyAll();
             return i;
          } catch (InterruptedException e) {
             Thread.currentThread().interrupt(); 
          }
          return 0;
       }
    }
    
    class Sieve implements Runnable {
    
        private Buffer in, out;
        private int myprime;
    
        public Sieve ( Buffer buf ) {
            in = buf;
        }
    
        public void run() {
            int n;
            myprime = in.get();
    
            if ( myprime < 0 ) {
                return;    // Done with input (base case)
            } else {
                System.out.println(myprime);
    
                out = new Buffer ( 5 );
    
                Sieve s = new Sieve ( out );
                Thread t = new Thread ( s );
                t.start();
    
                n = in.get();
                while ( n >= 0 ) {
                    if ( n % myprime != 0 ) {
                        out.put(n);
                    }
                    n = in.get();
                }
                out.put(n);  // output the negative to signal end
                        
            }
        }
    }
    
    
    public class FindPrimes {
        
        public static void main ( String[] args ) {
            
            Buffer b = new Buffer(5);
    
            Sieve s = new Sieve(b);
            Thread t = new Thread(s);
            t.start();
    
            for ( int i = 2; i <= 50; i++ ) {
                b.put(i);
            }
            b.put(-1);
        }
    }