Click on any of the image thumbnails for a more complete view of the screenshot below.
In this lab, you will get to explore working with threads, a shared resource, and critical sections. The basis of this lab is the problem 2.50 on page 157 of the Tanenbaum text - the problem of the shared restroom and men and women being excluded from using the restroom if someone of the opposite gender is already in the restroom.
Be sure that you have read the relevant content in Chapter 2 of the Tanenbaum text and have a general understanding of the concepts of mutual exclusion, critical sections, and semaphores. You will also need the have a C# IDE loaded on your machine (I'll use the Visual Studio 2005 C# Express IDE in the images below).
1. Begin a new C# Console Application in your IDE. I named mine "2601_Lab_1"
2. To use threads in C#, you need to use the "System.Threading" package of code (already built for you). So add the following to the top section of the code:
using
System.Threading;Now you have the ability to add threads to your program.
All console programs in C# begin in the "Main" function... so we'll need to add instructions here to create the worker threads. We can create a thread by doing something like:
Thread
t = new Thread(new ThreadStart(worker));The above code creates a new thread "t" and tells it to run the function "worker" by invoking the "Start" method (which tells the thread to run its assigned function - in this case, the "worker" function).
But what is the "worker" function? We'll have to write that:
static private void worker()The above code assigns a unique ID to each thread (using a global "id" shared by all threads - more on this later). The thread then loops 10 times, each time printing out that it's running and "sleeping." The "Sleep" method yields the processor to another thread/process for a set amount of time. In this case, we specify 0 time (so just yield the processor immediately and run again as soon as possible).
Now add the following code within the class (but outside any method):
static
int id = 0;You can view the following image to verify your code matches and you're ready to compile and run your program:
Now compile and run your code (using CTRL-F5 since it's a console app and you want it to "pause" when completed).
What output did you get? What does this tell you about the scheduling algorithm used in Windows?
Now that you know how to create threads in C#, let's make them act like the men and women in the Tanenbaum problem - requesting to use the restroom.
We can distinguish between whether the working thread is a woman or a man by its ID. Even IDs will be men, and odd IDs will be women, as in:
if (ID % 2 == 0) // manAnd what do we want the men and women to do? Request the use of the restroom, use it if available, and don't use it if it's not available. The following code has some random waiting in the Thread.Sleep function call (so they behave randomly in how much they wait before needing to use the bathroom), but the overall flow of the code has them request the use of the restroom, and if this succeeds, they leave the restroom after some random amount of time. If they don't get to use the restroom, we decrement the counter variable "i" (denoting that they haven't had their request fulfilled). "enter_success" is a boolean variable (bool) defined earlier in the "worker" function.
if (ID % 2 == 0)
// man
{
enter_success = ManEnter(ID);
if (enter_success)
{
Thread.Sleep(r.Next(1000));
ManLeaves(ID);
Thread.Sleep(r.Next(1000));
}
else
{
i--;
Thread.Sleep(r.Next(100));
}
}
else // woman
{
enter_success = WomanEnter(ID);
if (enter_success)
{
Thread.Sleep(r.Next(1000));
WomanLeaves(ID);
Thread.Sleep(r.Next(1000));
}
else
{
i--;
Thread.Sleep(r.Next(1000));
}
}
The amount of time to wait in the Thead.Sleep calls can be changed; there is nothing special about the 1000 and 100 values; but in adjusting them, you should see different behavior in the amount of time between requests to enter and leave the restroom.
Your code should now look like this:
Notice the ManEnter, ManLeave, WomanEnter, and WomanLeave functions are not defined yet. Let's define those next. From the invocation of these functions in the above code, we know that they take in one parameter, the integer representing the ID of the thread making the request; the "enter" functions return a bool determining if the request was successful, and the "leave" functions return nothing.
We'll use a semaphore (counting variable) to track who is in the restroom. If the semaphore is 0, then no one in the restroom; if the semaphore is positive, this indicates how many men are in the restroom; and when the semaphore is negative, this indicates how many women are in the restroom.
When a man enters the restroom, he must obtain a lock on the semaphore used to manage who is in the restroom. If it is greater than or equal to 0, he may enter (increasing it by one). If it is negative, the entry request fails.
To ensure mutual exclusion to a shared resource in C#, we use a Monitor class. To access the Monitor class, use the Enter and Exit methods and specify what we want to "lock" and "unlock". For example, I can "lock" the object "sign" using the code:
Monitor.Enter(sign);
I can then guarantee exclusive access to the object "sign" until I unlock the object using the Exit method, as in:
Monitor.Exit(sign);
Knowing how to achieve exclusive access to a shared resource, I can declare a Sign class and then instantiate it for the entire program (it will be shared among all the threads) as in:
class
Sign
{
public int
person_count = 0;
}
class Program
{
static int id = 0;
static Random r =
new Random();
static Sign sign
= new Sign();
...
Now that we have this shared resource, we can code the Enter and Exit functions. The following code implements the ManEnter logic:
static bool ManEnter(int id)
{
Monitor.Enter(sign);
if (sign.person_count >= 0)
{
sign.person_count++;
Console.WriteLine("Man " + id + " enters the bathroom");
Monitor.Exit(sign);
return true;
}
else
{
Monitor.Exit(sign);
return false;
}
}
And this code implements the ManLeaves logic:
static void ManLeaves(int id)
{
Monitor.Enter(sign);
sign.person_count--;
Console.WriteLine("Man " + id + " leaves the bathroom");
Monitor.Exit(sign);
}
The code for the female version of these functions is nearly identical; we just invert the ++ to -- and check for <= 0 (instead of >= 0):
static bool WomanEnter(int id)
{
Monitor.Enter(sign);
if (sign.person_count <= 0)
{
sign.person_count--;
Console.WriteLine("Woman " + id + " enters the bathroom");
Monitor.Exit(sign);
return true;
}
else
{
Monitor.Exit(sign);
return false;
}
}
static void WomanLeaves(int id)
{
Monitor.Enter(sign);
sign.person_count++;
Console.WriteLine("Woman " + id + " leaves the bathroom");
Monitor.Exit(sign);
}
Now run the code and see the behavior. What do
you notice about whether men and women are allowed in the restroom at the same
time? Is there anything wrong here, or does this code correctly solve the
problem? Run it a few times (even changing the random "sleep" times) to
see if anything changes and if a woman and a man are ever in the restroom at the
same time.
Here is a sample output from the program:
The following code is the entire solution; use it to verify what you entered matches (in case you get any errors).
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
namespace _2601_Lab_1
{
class Sign
{
public int person_count = 0;
}
class Program
{
static int id = 0;
static Random r = new Random();
static Sign sign = new Sign();
// -------------------------------------------------------------------
static void Main(string[] args)
{
for (int i = 0; i < 10; i++)
{
Thread t = new Thread(new ThreadStart(worker));
t.Start();
}
}
// -------------------------------------------------------------------
static bool ManEnter(int id)
{
Monitor.Enter(sign);
if (sign.person_count >= 0)
{
sign.person_count++;
Console.WriteLine("Man " + id + " enters the bathroom");
Monitor.Exit(sign);
return true;
}
else
{
Monitor.Exit(sign);
return false;
}
}
// -------------------------------------------------------------------
static void ManLeaves(int id)
{
Monitor.Enter(sign);
sign.person_count--;
Console.WriteLine("Man " + id + " leaves the bathroom");
Monitor.Exit(sign);
}
// -------------------------------------------------------------------
static bool WomanEnter(int id)
{
Monitor.Enter(sign);
if (sign.person_count <= 0)
{
sign.person_count--;
Console.WriteLine("Woman " + id + " enters the bathroom");
Monitor.Exit(sign);
return true;
}
else
{
Monitor.Exit(sign);
return false;
}
}
// -------------------------------------------------------------------
static void WomanLeaves(int id)
{
Monitor.Enter(sign);
sign.person_count++;
Console.WriteLine("Woman " + id + " leaves the bathroom");
Monitor.Exit(sign);
}
// -------------------------------------------------------------------
static private void worker()
{
int ID = id;
id++;
bool enter_success;
Thread.Sleep(r.Next(10000));
for(int i=0; i < 10; i++)
{
if (ID % 2 == 0) // man
{
enter_success = ManEnter(ID);
if (enter_success)
{
Thread.Sleep(r.Next(1000));
ManLeaves(ID);
Thread.Sleep(r.Next(1000));
}
else
{
i--;
Thread.Sleep(r.Next(100));
}
}
else // woman
{
enter_success = WomanEnter(ID);
if (enter_success)
{
Thread.Sleep(r.Next(1000));
WomanLeaves(ID);
Thread.Sleep(r.Next(1000));
}
else
{
i--;
Thread.Sleep(r.Next(1000));
}
}
}
}
}
}