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);
}
}