CS-230 Programming Assignment #1

Due date: Friday, November 11, 1994 in class.

In this assignment, we give you part of a working thread system; your job is to complete it, and then to use it to solve several synchronization problems.

Properly synchronized code should work no matter what order the scheduler chooses to run the threads on the ready list. In other words, we should be able to put a call to Thread::Yield (causing the scheduler to choose another thread to run) anywhere in your code where interrupts are enabled without changing the correctness of your code. You will be asked to write properly synchronized code as part of the later assignments, so understanding how to do this is crucial to being able to do the project.

To aid you in this, code linked in with Nachos will cause Thread::Yield to be called on your behalf in a repeatable but unpredictable way. Nachos code is repeatable in that if you call it repeatedly with the same arguments, it will do exactly the same thing each time. However, if you invoke “nachos -rs #”, with a different number each time, calls to Thread::Yield will be inserted at different places in the code.

Make sure to run various test cases against your solutions to these problems; for instance, for part two, create multiple producers and consumers and demonstrate that the output can vary, within certain boundaries.

There should be no busy-waiting in any of your solutions to this assignment.

1. Implement locks and condition variables. You may either use semaphores as a building block, or you may use more primitive thread routines (such as Thread::Sleep). We have provided the public interface to locks and condition variables in synch.h. You need to define the private data and implement the interface. Note that it should not take you very much code to implement either locks or condition variables. A lock is a variable that handles the operations Acquire() and Release(). When lock::Acquire() is executed and the lock is free, the lock state is simply set to busy and the function returns. If the lock is busy, then the thread calling Acquire() blocks until the lock is free. When lock-¿Release() is called the lock is set to free and a process blocked on the lock is made ready. If a thread releases a lock it has not acuqired, the lock does not change. A condition variable handles the operations Wait(lock), Signal(lock), and Broadcast(lock). When a thread executes condition::Wait(lock), it releases the lock, sleeps and then acquires the lock again. When a thread executes condition::Signal(lock) a thread waiting on the condition is made ready but the lock is not released. Executing condition::Broadcast(lock) wakes up all threads waiting on the condition.

2. Implement producer/consumer communication through a bounded buffer, using locks and condition variables. (A solution to the bounded buffer problem is described in Silberschatz and Galvin, using semaphores.)

The producer places characters from the string ”Hello world” into the buffer one character at a time; it must wait if the buffer is full. The consumer pulls characters out of the buffer one at a time and prints them to the screen; it must wait if the buffer is empty. Test your solution with a multi-character buffer and with multiple producers and consumers. Of course, with multiple producers or consumers, the output display will be gobbledygook; the point is to illustrate that multiple producers and consumers can share the same buffer.

3. The local laundromat has just entered the computer age. As each customer enters, he or she puts coins into slots at one of two stations and types in the number of washing machines he/she will need. The stations are connected to a central computer that automatically assigns available machines and outputs tokens that identify the machines to be used. The customer puts laundry into the machines and inserts each token into the machine indicated on the token. When a machine finishes its cycle, it informs the computer that it is available again. The computer maintains an array available[NMACHINES] whose elements are non-zero if the corresponding machines are available (NMACHINES is a constant indicating how many machines there are in the laundromat), and a semaphore nfree that indicates how many machines are available.

The code to allocate and release machines is as follows:

int allocate() /* Returns index of available machine.*/
{
  int i;
  P(nfree); /* Wait until a machine is available */
  for (i=0; i < NMACHINES; i++)
    if (available[i] != 0) {
      available[i] = 0;
      return i;
    }
}
release(int machine) /* Releases machine */
{
  available[machine] = 1;
  V(nfree);
}

The available array is initialized to all ones, and nfree is initialized to NMACHINES.

(a) It seems that if two people make requests at the two stations at the same time, they will occasionally be assigned the same machine. This has resulted in several brawls in the laundromat, and you have been called in by the owner to fix the problem. Assume that one thread handles each customer station. Explain how the same washing machine can be assigned to two different customers.

(b) Modify the code to eliminate the problem.

(c) Re-write the code to solve the synchronization problem using locks and condition variables instead of semaphores.

4. You’ve just been hired by Mother Nature to help her out with the chemical reaction to form water, which she doesn’t seem to be able to get right due to synchronization problems. The trick is to get two H atoms and one O atom all together at the same time. The atoms are threads. Each H atom invokes a procedure hReady when it’s ready to react, and each O atom invokes a procedure oReady when it’s ready. For this problem, you are to write the code for hReady and oReady. The procedures must delay until there are at least two H atoms and one O atom present, and then one of the procedures must call the procedure makeWater (which just prints out a debug message that water was made). After the makeWater call, two instances of hReady and one instance of oReady should return. Write the code for hReady and oReady using either semaphores or locks and condition variables for synchronization. Your solution must avoid starvation and busy-waiting.