SOURCE FILE: foodPass2.cpp
/*
In this program the server puts dishes full of food on one end of
a table and diners pass the dishes down to the other end of the
table where they are collected by the busser.
This is a more "advanced" version of the program where there are
two diners between each pair of trivets, rather than just one.
The job of each diner is to loop these actions:
* I become ready to "experience" the next dish, (basically "I
become hungry")
* I acquire the next dish on the trivet next to me in direction
"north" (exclusively)
* IF the dish contains food THEN I eat some of the food,
* IF the diner opposite me has already taken food from this dish
THEN when available, I place the dish on the trivet next to me
in direction south.
* IF the dish I just experienced contains the check THEN I exit,
ELSE I go back to the start of this loop.
The job of the server is to serve dishes by placing them one
after another onto trivet 0 (the northernmost trivet) and then
exit.
The job of the busser is to remove dishes from the last trivet
(southernmost) one after another until removing the check.
The threads implementing the server, busser, and diners have to
coordinate. Their goals are to
1. avoid as much busy waiting as possible
2. achieve as much parallel processing as possible
3. avoid the possibility that a person could acquire the same dish twice
4. avoid the possiblity that a person could "serve from an empty dish"
5. avoid the possibility that a person could place one dish on top of another.
6. make sure everyone gets finished eating and the check gets paid.
7. make sure that the only threads which are involved in
regulating access to a trivet are the threads that either a)
currently are using the trivet, b) currently are trying to get
access to the trivet (in entry code), or c) currently are
releasing the trivet (executing exit code):
Your grade depends on how well you achieve these goals.
*/
#include <iostream>
#include <sched.h>
#include <time.h>
#include <pthread.h>
#include <string>
#include "sem.h"
using namespace std ;
/* ######################################## */
/* Global Variables */
/* ######################################## */
//const int numTrivets = 6 ;
const int numTrivets = 3 ;
const int numDiners = 2*(numTrivets-1);
const int maxDishNames = 13 ;
//const int numDishNames = 13 ;
const int numDishNames = 5 ;
const int west = 0 ;
const int east = 1 ;
/* When trivet[i]==j it means that dish #j is on trivet #i.*/
int trivet[numTrivets] ;
/* dishName[j] is the name of dish #j */
string dishName[maxDishNames];
/* Here declare all the semaphores and other variables you want
to use for synchronization */
/* diner_t[][] and server_t are global variables used to
represent the dynamically-created threads. */
pthread_t ** diner_t ;
pthread_t server_t ;
/* ######################################## */
/* "Special" Global Variables */
/* ######################################## */
/* Code in sem.cpp "expects" the two variables below to
be here. This particular program does not use
"checking." */
/* "Checking" is just a flag that you set to 1 if you
want lots of debugging messages and set to 0
otherwise. The semaphore code in sem.cpp imports
"checking". Therefore the semaphore operations will
write lots of messages if you set checking=1. */
int checking ;
/* In some programs, we use the "stdoutLock" variable
declared below to get intelligible printouts from
multiple concurrent threads that write to the standard
output. (There has to be something to prevent the
output of the threads from interleaving unintelligibly
on the standard output, and we can't use semaphores if
the semaphore code is writing messages too.)
To print a message to standard output, a thread first
locks standard output, then writes, then unlocks
standard output. See files sem.cpp or conc.cpp for
examples of code that write messages in this manner.
WARNING: DON'T change how the locking of standard
output is done until you've thought a WHOLE lot about
the consequences. In particular, using semaphores to do
the job of stdoutLock can cause "infinite recursion"
under certain circumstances. The reason is that the
semaphore code itself imports "stdoutLock" and writes
messages when the "checking" variable is set to 1. */
pthread_mutex_t stdoutLock ;
/* ################################################## */
/* init */
/* ################################################## */
void init()
{
int index;
srandom(time((time_t *) 0)); /* INITIALIZE RANDOM NUMBER GENERATOR */
checking = 0 ;
/* Initialize the "special lock" that is used only to get
exclusive access to the screen. */
if ( 0!=pthread_mutex_init(&stdoutLock, NULL) )
{ cout << "MUTEX INITIALIZATION FAILURE!" << endl; exit(-1) ;}
/* Here initialize your semaphores and other variables you are using for
synchronization. */
/* Initialize the trivets to indicate that each contains
"no dish." */
for (index=0; index<numTrivets; index++)
{
trivet[index] = 0;
}
/* Allocate an array of numDiners/2 pointers to pthread_t and make
diner_t a pointer to the base of that array.
diner_t
|
|
V
-----
| | ----> pthread_t
-----
| | ----> pthread_t
-----
| | ----> pthread_t
-----
| | ----> pthread_t
-----
This makes "diner_t" equivalent to the *name* of an array of pointers
to pthread_t. */
diner_t = new pthread_t * [numDiners/2] ;
/* For each row i of diner_t (diner_t[i]), allocate an array
of two pthread_t's and make diner_t[i] a pointer to the
base element of the array.
diner_t
|
|
V
----- ---------
| | ---->|pth|pth|
----- ---------
| | ---->|pth|pth|
----- ---------
| | ---->|pth|pth|
----- ---------
| | ---->|pth|pth|
----- ---------
This makes diner_t in effect the name of a 2D array of pthread_t's,
having numDiners/2 rows and two columns. */
for (index=0; index<(numDiners/2); index++)
diner_t[index] = new pthread_t[2] ;
/* Give some mnemonic names to the dishes. The first name is
used for an empty trivet. The last name denotes the check
(bill) for the meal. This is coded so no changes are needed
here as long as the value of "numDishNames" is between 2 and
13. */
dishName[0]="no dish";
dishName[1]="vegetable soup" ;
dishName[2]="bread and butter" ;
dishName[3]="beets and chickpeas" ;
dishName[4]="hardboiled eggs" ;
dishName[5]="calf tongue" ;
dishName[6]="baked potato" ;
dishName[7]="string beans" ;
dishName[8]="rack of lamb" ;
dishName[9]="salad" ;
dishName[10]="coffee" ;
dishName[11]="flan" ;
dishName[numDishNames-1]="check" ;
}
/* ################################################## */
/* DelayAsMuchAs */
/* ################################################## */
void delayAsMuchAs (int limit)
{
int time, step;
time=(int)random()%limit;
for (step=0;step<time;step++) sched_yield() ;
}
/* ################################################## */
/* Server */
/* ################################################## */
/* The mother thread spawns a child thread that executes this
function. This function carries out the job of the server at
the restaurant. */
void * Server(void * ignore)
{
int i, j, delayLimit=100 ;
for (i=1; i<numDishNames; i++)
{
/* I delay a random time before I "feel like" placing
another dish on the table.*/
delayAsMuchAs(delayLimit);
/* When the trivet is available, I place the dish on trivet
#0. */
/* Here do a synchronization task. One thing you need to
do is be sure that you are not going to place a dish on
a trivet that alreay has a dish on it. *DO NOT* just
busy-wait until you see that the trivet is empty. */
trivet[0]=i; /* put dish #i onto trivet #0. */
pthread_mutex_lock(&stdoutLock) ;
cout << "Server places " << dishName[trivet[0]]
<< " on trivet #0." << endl ;
pthread_mutex_unlock(&stdoutLock);
/* Here you may want to a synchronization task --
something that "opens the door" for diners #0 and #1 to
get access to the new dish. */
}
}
/* ################################################## */
/* Diner */
/* ################################################## */
/* The mother thread spawns child threads that execute this
function. This function carries out the job of one of the
diners at the restaurant. */
void * Diner(void * postnPtr)
{
/* Declare some some local variables (these are not
shared). Type cast the parameter to recover "position"
-- which tells me the position at which I am seated at
the table. */
int position = (int) postnPtr ;
int i, j, delayLimit=100 ;
int I_pass = 0 ;
for (i=1; i<numDishNames; i++)
{
/* I delay a random time before I "feel like" picking up
the next dish.*/
delayAsMuchAs(delayLimit);
/* When available, I pick up the next new dish on my
"north" side. The number of the trivet is position/2.
The "geometry" of the table is as diagrammed below.
EAST
diner diner
1 3
(NORTH) Server T T T Busser (SOUTH)
diner diner
0 2
WEST
*/
/* wait for a new dish to appear -- postion/2 is the
trivet number and position%2 determines west versus
east. */
/* After some sequence of synchronization actions you will
do here, you find yourself with a new dish before you and
a spoon in your hand for serving */
/* I declare what I am doing */
pthread_mutex_lock(&stdoutLock) ;
cout << "Diner number " << position/2 ;
if (position%2 == west) cout << " west "; else cout << " east ";
if (i<numDishNames-1) cout << "enjoys ";
else cout << " examines " ;
cout << dishName[trivet[position/2]] << "." << endl ;
pthread_mutex_unlock(&stdoutLock);
/* I delay a random time to simulate the time it takes
for me to serve myself some of what is on the dish --
or look at the check. */
delayAsMuchAs(delayLimit);
/* Now "eat" or "examine." */
/* I have to pass the plate if the diner opposite me has already
taken some of the food off the dish. */
/* Here I figure out if the diner across from me has
already accessed the current dish. If so, then I am
the second and it is my job to pass the dish down to
the next trivet. Accordingly, I will set I_pass to 1
here if I am the second to access the dish. */
/* Do any necessary synchronization tasks here. */
/* If I'm supposed to pass down the plate then I wait for
the trivet to the south to become empty. */
if (I_pass)
{
/* Do any necessary synchronization tasks here. */
/* Tell the world what I'm doing. */
pthread_mutex_lock(&stdoutLock) ;
cout << "Diner number "<< position/2 ;
if (position%2 == west) cout << " west"; else cout << " east";
if ( (i==numDishNames-1) && (position/2==numTrivets-2) ) cout << " pays check and" ;
cout << " moves "
<< dishName[trivet[(position/2)]] << " from trivet #"
<< position/2 << " to trivet #" << (position/2)+1 << "." << endl;
pthread_mutex_unlock(&stdoutLock);
/* transfer the dish on my north side to trivet on my south side.
*/
trivet[(position/2)+1]=trivet[position/2] ;
/* mark trivet on north side as empty */
trivet[position/2]=0;
/* Do any necessary synchronization tasks here. */
} /* end of actions performed by the passer */
} /* end of main for-loop */
} /* end of Diner function */
/* ################################################## */
/* Busser */
/* ################################################## */
/*
The mother thread spawns children and then executes this function. This
is convenient because this function should be the last to exit. This
function carries out the job of the busser at the restaurant.
*/
void * Busser (void * ignore)
{
int i, j, delayLimit=100 ;
for (i=1; i<numDishNames; i++)
{
/* I delay a random time before I "feel like" bussing another
dish.*/
delayAsMuchAs(delayLimit);
/* When another dish is on the trivet to my north I remove it. */
/* Do any necessary synchronization tasks here. */
pthread_mutex_lock(&stdoutLock) ;
cout << "Busser removes "
<< dishName[trivet[numTrivets-1]] << " from trivet #"
<< numTrivets-1<< "." << endl ;
pthread_mutex_unlock(&stdoutLock);
trivet[numTrivets-1]=0; // remove the dish.
/* Do any necessary synchronization tasks here. */
}
}
/* ################################################## */
/* Main */
/* ################################################## */
main()
{
init();
cout << endl << endl;
cout << "Welcome to the restaurant!" << endl ;
cout << numDiners << " will be dining." << endl ;
cout << "The meal will consist of " << numDishNames-2 << " dishes." << endl;
cout << "Bon appetite!" << endl ;
cout << endl << endl;
int i;
for (i=0; i<numDiners; i++)
{
if (0!=pthread_create(&diner_t[i/2][i%2], NULL, Diner, (void *) i))
{cout << "THREAD CREATION FAILURE!" << endl; exit(-1) ;}
if (0!=pthread_detach(diner_t[i/2][i%2]))
{cout << "THREAD DETACHMENT FAILURE!" << endl ; exit(-1) ;}
}
if (0!=pthread_create(&server_t, NULL, Server, (void *) 0))
{cout << "THREAD CREATION FAILURE!" << endl; exit(-1) ;}
if (0!=pthread_detach(server_t))
{cout << "THREAD DETACHMENT FAILURE!" << endl ; exit(-1) ;}
Busser((void *)0) ;
cout << endl << endl;
cout << "Thank you for coming!" << endl ;
cout << endl << endl;
}