I am studying mutual exclusion in college, and we just covered the producer/consumer problem. The class does not involve writing code, but I decided to implement a bounded buffer version of this problem. I have never written a multi-threaded program before, nor have I written a program with mutual exclusion before, so I decided to request a review here.
I implemented three variations, a busy-waiting variation, a Semaphore variation, and a Monitor variation. All of these reside in a class named Program, which is needed for the threading. The Monitor variation looks as if there should be a simpler solution with fewer variables. Is this so?
This is the part of the code that never changes:
const int buffSize = 10; static char[] buffer = new char[buffSize]; static int valuesToProduce = 95; static void Main(string[] args) { Thread p = new Thread(new ThreadStart(Program.produce)); Thread c = new Thread(new ThreadStart(Program.consume)); p.Start(); c.Start(); } This is the busy-waiting producer/consumer and their related global variable:
static int avail = 0; static void produce() { for(int i=0; i<valuesToProduce; i++) { while (avail == buffSize) { }; buffer[i % buffSize] = (char)(32 + i % 95); Console.WriteLine("Produced: {0}", buffer[i % buffSize]); avail++; } } static void consume() { for (int i = 0; i < valuesToProduce; i++) { while (avail < 1) { }; char c = buffer[i % buffSize]; Console.WriteLine("Consumed: {0}", buffer[i % buffSize]); avail--; } } This is the Semaphore implementation:
private static Semaphore isFull = new Semaphore(buffSize, buffSize); private static Semaphore isEmpty = new Semaphore(0, buffSize); static void produce() { for (int i = 0; i < valuesToProduce; i++) { isFull.WaitOne(); buffer[i % buffSize] = (char)(32 + i % 95); Console.WriteLine("Produced: {0}", buffer[i % buffSize]); isEmpty.Release(1); } } static void consume() { for (int i = 0; i < valuesToProduce; i++) { isEmpty.WaitOne(); char c = buffer[i % buffSize]; Console.WriteLine("Consumed: {0}", c); isFull.Release(1); } } And this is the Monitor implementation:
static int avail = 0; private static object _buffer = new object(); private static object isFull = new object(); private static object isEmpty = new object(); static void produce() { for (int i = 0; i < valuesToProduce; i++) { while (avail == buffSize) { Monitor.Enter(isFull); Monitor.Wait(isFull); Monitor.Exit(isFull); } Monitor.Enter(_buffer); buffer[i % buffSize] = (char)(32 + i % 95); avail++; Console.WriteLine("Produced: {0}", buffer[i % buffSize]); Monitor.Exit(_buffer); Monitor.Enter(isEmpty); Monitor.Pulse(isEmpty); Monitor.Exit(isEmpty); } avail++; } static void consume() { for (int i = 0; i < valuesToProduce; i++) { while (avail < 1) { Monitor.Enter(isEmpty); Monitor.Wait(isEmpty); Monitor.Exit(isEmpty); } Monitor.Enter(_buffer); char c = buffer[i % buffSize]; avail--; Console.WriteLine("Consumed: {0}", buffer[i % buffSize]); Monitor.Exit(_buffer); Monitor.Enter(isFull); Monitor.Pulse(isFull); Monitor.Exit(isFull); } }