package nachos.threads; import nachos.machine.*; import java.util.Random; /** * A Tester for the Condition2 class */ public class Condition2Test { /** * ProdConsBuffer class, which implements a producer/consumer buffer. * This implementation uses several condition variables, and some * "tricks" to synchronize everything at the end of the execution. This * could be done better with a join() call to wait for threads to * finish. But at the moment, the main thread waits to be signaled * that all producers and consumers have exited. This demonstrate that * synchronization primitives can be built on top of others, or each other. */ private static class ProdConsBuffer { /** * Constructor: takes as parameter the maximum number * of actions (i.e., producing or consuming an item) * before all producers and consumers call it quit. This * is to avoid an infinite execution. */ public ProdConsBuffer(int maxNumActions) { /* Initially no actions have been performed */ this.numActions = 0; this.maxNumActions = maxNumActions; this.isDone = false; /* Initially no threads are done */ this.numFinishedThreads = 0; /* Initially the buffer is empty */ this.lastItemIndex = -1; this.buffer = new int[ProdConsBuffer.maxNumItems]; for (int i=0; i < ProdConsBuffer.maxNumItems; i++) this.buffer[i] = -1; this.isEmpty = true; this.isFull = false; /* Create the mutex and the two condition variables */ this.mutex = new Lock(); this.isNotEmptyCond = new Condition2(this.mutex); this.isNotFullCond = new Condition2(this.mutex); this.isOverCond = new Condition2(this.mutex); /* Create the RNG to generate random items */ this.rng = new Random(); } /** * Method to consume an item. This method is NOT thread-safe * and should by called within a critical section via this.mutex. * This function should never be called on an empty buffer. */ public int consumeItem() { int item; /* Is the buffer empty??? */ if (lastItemIndex == -1) { System.out.println("Error: Can't consume item because buffer is empty!!"); return -1; } /* Consume the item */ lastItemIndex--; item = buffer[lastItemIndex+1]; buffer[lastItemIndex+1] = -1; /* Sanity check */ if (item == -1) { System.out.println("Error: Consumed an invalid item!!"); return -1; } /* Update isEmpty and isFull */ isEmpty = (lastItemIndex == -1); isFull = false; /* One additional action was performed */ isDone = (++numActions >= maxNumActions); /* if we're done, wake up all sleepers */ if (isDone) { this.isNotFullCond.wakeAll(); this.isNotEmptyCond.wakeAll(); } return item; } /** * Method to produce an item. This method is NOT thread-safe * and should by called within a critical section via this.mutex. * This function should never be called on a full buffer. */ public void produceItem(int item) { /* Is the buffer full?? */ if (lastItemIndex == maxNumItems - 1) { System.out.println("Error: Can't produce item because buffer is full!!"); return; } /* Sanity check */ if (buffer[lastItemIndex+1] != -1) { System.out.println("Error: Produced item at an invalid position!!"); return; } /* Produce an item */ lastItemIndex++; buffer[lastItemIndex] = item; /* Update isFull and isEmpty */ isFull = (lastItemIndex == maxNumItems-1); isEmpty = false; /* One additional action was performed */ isDone = (++numActions >= maxNumActions); /* if we're done, wake up all sleepers */ if (isDone) { this.isNotFullCond.wakeAll(); this.isNotEmptyCond.wakeAll(); } return; } /** Method to generate a random number */ public int generateRandomItem() { return rng.nextInt(50); /* between 0 and 50 */ } /* Lock for mutual exclusion and condition variables */ public Lock mutex; /* Condition variables */ public Condition2 isNotEmptyCond; /* signaled when the buffer becomes non-empty */ public Condition2 isNotFullCond; /* signaled when the buffer becomes non-full */ public Condition2 isOverCond; /* signaled by each finishing prod or cons */ /* Booleans indicating buffer state */ public boolean isEmpty; public boolean isFull; /* The buffer of elements */ private static final int maxNumItems = 10; private int lastItemIndex; private int buffer[]; /* The global counter of actions and flag */ private int maxNumActions; private int numActions; public boolean isDone; /* The number of threads that are finished */ public int numFinishedThreads; /* Random number generator */ private Random rng; } /** * Producer class, which implements a producer thread * that puts data in a buffer. */ private static class Producer implements Runnable { /* Constructor */ Producer(int who, ProdConsBuffer buffer) { this.buffer = buffer; this.who = who; } public void run() { System.out.println("** Producer #"+who+" begins"); /* Loop */ while (true) { /* Acquire the mutex */ buffer.mutex.acquire(); /* If the buffer is full, wait on isNotFullCond. This is * in a while loop to avoid spurious wake-ups */ while (!buffer.isDone && buffer.isFull) { System.out.println("** Producer #"+who+" waits for the buffer to not be full"); buffer.isNotFullCond.sleep(); /* releases the mutex and reacquires * it when it wakes up */ } /* I just woke up, and perhaps it's because it's all over * in which case I exit from my main loop */ if (buffer.isDone) { buffer.mutex.release(); break; } /* Produce an item */ int producedItem = buffer.generateRandomItem(); System.out.println("** Producer #"+who+" produces "+producedItem); buffer.produceItem(producedItem); /* Wake up potential consumers */ buffer.isNotEmptyCond.wake(); /* Release the mutex */ buffer.mutex.release(); /* Yield so that somebody else has a chance to run */ KThread.yield(); } System.out.println("** Producer #"+who+" exits"); /* Signal that the thread is finished */ buffer.mutex.acquire(); buffer.numFinishedThreads++; buffer.isOverCond.wake(); buffer.mutex.release(); } /* The Prod/Cons buffer */ private ProdConsBuffer buffer; /* An ID for printing out information */ private int who; } private static class Consumer implements Runnable { Consumer(int who, ProdConsBuffer buffer) { this.buffer = buffer; this.who = who; } public void run() { System.out.println("** Consumer #"+who+" begins"); /* Loop */ while (true) { /* Acquire the mutex */ buffer.mutex.acquire(); /* If the buffer is empty, wait on isNotEmptyCond. This is * in a while loop to avoid spurious wake-ups */ while (!buffer.isDone && buffer.isEmpty) { System.out.println("** Consumer #"+who+" waits for the buffer to not be empty"); buffer.isNotEmptyCond.sleep(); /* releases the mutex and reacquires * it when it wakes up */ } /* I just woke up, and perhaps it's because it's all over * in which case I exit from my main loop */ if (buffer.isDone) { buffer.mutex.release(); break; } /* Consume an item */ int consumedItem = buffer.consumeItem(); System.out.println("** Consumer #"+who+" consumes item "+consumedItem); /* Wake up potential producers */ buffer.isNotFullCond.wake(); /* Release the mutex */ buffer.mutex.release(); /* Yield so that somebody else has a chance to run */ KThread.yield(); } System.out.println("** Consumer #"+who+" exits"); /* Signal that the thread is finished */ buffer.mutex.acquire(); buffer.numFinishedThreads++; buffer.isOverCond.wake(); buffer.mutex.release(); } /* The Prod/Cons buffer */ private ProdConsBuffer buffer; /* An ID for printing out information */ private int who; } /** * Tests whether this module is working. */ public static void runTest() { System.out.println("**** Condition testing begins ****"); /* Create the buffer, with a specified max # of actions */ ProdConsBuffer buffer = new ProdConsBuffer(maxNumActions); /* Create producer threads and fork them*/ KThread producers[] = new KThread[numProducers]; for (int i=0; i < numProducers; i++) { producers[i] = new KThread(new Producer(i,buffer)).setName("producer thread #"+i); producers[i].fork(); } /* Create consumer threads and fork them*/ KThread consumers[] = new KThread[numConsumers]; for (int i=0; i < numConsumers; i++) { consumers[i] = new KThread(new Consumer(i,buffer)).setName("consumer thread #"+i); consumers[i].fork(); } /* Wait for the prod/cons execution to be over */ buffer.mutex.acquire(); while (buffer.numFinishedThreads != numConsumers + numProducers) { buffer.isOverCond.sleep(); } buffer.mutex.release(); System.out.println("**** Condition testing ends ****"); } private static final int maxNumActions = 100000; private static final int numProducers = 10; private static final int numConsumers = 100; }