Home Schedule Reading Presentations Projects People
Mac Logo Windows Logo Minix Logo Sun Solaris Logo Linux Logo

Project 5: The Rum and Coke Enthusiast's Problem

Assignment Day November 08, 2011 (Tuesday)
Due Date November 15, 2011 (Tuesday)

{In progress}

 

Collaboration Policy - Read Carefully

You must work on this project individually, but you may discuss this assignment with other students in the class and ask and provide help in useful ways, preferable over our email list so we can all benefit from your great ideas.

Objective:

The goal of this project is to expose you to the concepts of threads, synchronization and deadlocks. You will be required to demonstrate your understanding of synchronization by programming in C or C++. No projects in Java will be accepted. Your program must COMPILE and RUN on odin.

Key Concepts:

  • Programming with threads
  • Synchronization
  • Deadlock

Problem Description:

A group of R&C enthusiasts gathers for the Annual R&C FAT (RCFAT) Tuesday convention.

Central to the convention is the R&C competition, where participants compete in making the perfect R&C. The competition starts when the traditional triangular oak tables (triple-OAT), each accommodating three participants at a time, are brought in. There are enough tables to accommodate all participants.

To make an R&C, each enthusiast needs at least three ingredients: rum, coke, and a lime. This is based on the original recipe, from somewhere in Cuba, perhaps Havana, between the years of 1936 and 1948.

The center of each oak table has a hole, the hole produces each ingredient in ample supply, henceforth called "the supplier". At each table, one enthusiast has his own rum, a second has her/his own coke, and a third has her own lime. The game begins when the big hole spits out two of the ingredients on the table allowing one enthusiast to complete his drink. The appropriate enthusiast combines her/his ingredients, shakes them, pours her drink through a strainer into an ice-cold, adds ice and and samples (drinks). When the appropriate enthusiast is done, he or she wakes up the big hole, who then spits out two more ingredients (at random), thus unblocking another enthusiast.

Notice that there are situations where a deadlock may occur. For example, suppose the hole spits out the rum and the lime on the table. If the the enthusiast with coke grabs BOTH the rum and the lime off the table he may shake, pour and drink. But suppose that the enthusiast with lime grabs the rum, BEFORE the enthusiast with coke AND waits for the supplier to put out the coke then a deadlock occurs.

Assignment:

Your assignment is to write programs for the R&C enthusiasts and the big hole that coordinates the access of the R&C ingredients.

Implement TWO solutions to the R&C enthusiast's problem, one that may deadlock and another that AVOIDS deadlocks. In the first solution you will need to ALLOW and DETECT deadlocks. In the second solution you will extend the first algorithm and use a deadlock avoidance algorithm in order to never deadlock.

{2011 - you only need to do one - you chose the one you like to do}

The classical solution to the R&C problem is to: Use a semaphore for each combination (of two) ingredients. This solution is shown in the below pseudo-code. In contrast to the solution given below, a deadlock MAY occur if there is one semaphore for each of the individual ingredients as demonstrated earlier.

// PSEUDO Code No Deadlock

   semaphore rum_coke     = 0 // waiting for rum and coke
   semaphore rum_lime     = 0 // waiting for rum and lime
   semaphore coke_lime    = 0 // waiting for coke and lime
 The supplier() and rc_mixer() procedures are listed below:
 supplier()
   	while( true )
   		{
   		pick a random number from 1-3
   			{		
   			if random number is 1
   				{
   				// put these two  ingredients on table
   				signal( rum_coke )
   				}	
   			else if random number is 2
   				{
   				// put these two  ingredients on table
   				signal( rum_lime )  // put on table
   				}
   			else if random number is 3
   				{		
   				// put these two  ingredients on table
   				signal( coke_lime )  // put on table
   				}
   			} // done picking random number 
 		wait( doneDrinking )
		} /* while */
 
 // the rc mixer that has lime
 rc_mixer()
   	while( true )
   		{
   		wait( rum_coke ); /* picks up rum and coke */
 		// shake, pour and drink
 		signal( doneDrinking );
   		}  // while
 	// the rc mixer that has coke
 	// the rc mixer that has rum


Semaphores:

For this assignment you must use semaphores. Operations on semaphores include post (V), wait (P) and initialize.

 #include <semaphore.h>
 int sem_post( sem_t *sem );
 int sem_wait( sem_t *sem );
 int sem_init( sem_t *sem );
 

Example use of these operations is demonstrated in a simple example program that is in the project3/ directory. You may also check the man pages for more details.

Useful PThread Functions:

For this assignment you will need to use threads. Below are some functions
that you may need.
 #include <pthread.h>
 int  pthread_create(pthread_t  *  thread, pthread_attr_t * attr, void * (*start_routine)(void *), void * arg);
 pthread_mutex_lock( blockmon );
 pthread_mutex_unlock(blockmon);
mutexes can be statically or dynamically allocated;
   pthread_mutex_t *mut;
   mut = (pthread_mutex_t *) calloc( 1, sizeof(pthread_mutex_t) );
C++ syntax:
   mut = new pthread_mutex_t();
mutexes must be initialized after allocation:
 /* the attribute parameter is default because of NULL*/
 pthread_mutex_t *mut;
 pthread_mutex_init( mut, NULL ); /* 2nd argument are attributes of mutex */
statically allocated mutex can be initialized via a macro:
 pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER; 

PThread Tutorials and Documentation:

For more details on pthreads and a tutorial please check out the below resources :

 

A Simple P-Thread Program Example:

Project directory (from 2006):

http://www.cs.uga.edu/~maria/classes/4730-Fall-2006/project3/

Helpful programs and directories from above project directory:

 		hello-world
   	race-condition
   	semaphore-example

Some Hints:

Here is example pseudo code on how to send in a struct to a thread, note this example is purely for illustration:

/* Defining RC_mixer structure */
typedef struct
     {
     ...	/* other varialbes */
     int  id;   /* The rum & coke enthusiast's id: 0 to n-1 */
     } RC_mixer_struct;


/* Run by Rum & Coke Enthusiast thread */
void *rc_mixer( void *v ) 
     {
     int 		my_id;
     RC_mixer_struct 	*rc_mixer_struct;

     rc_mixer_struct = (RC_mixer_struct *) v;
     my_id = rc_mixer_struct->id;

     ...		/* other code */

     }

int main( int argc, char ** argv )
     {
     RC_mixer_struct          rc_mixer_struct[MAXTHREADS];
     pthread_t                threads[MAXTHREADS];
     int rc;

     /* fill in struct */

     ...
	
     /* set i to the 4th thread */
     i = 4;

     /* create thread */
     /* arg1 - thread+i 4th thread[]  */
     /* arg2 - NULL: attributes are default */
     /* arg3 - pointer to rc_mixer() function */
     /* arg4 - structure associated with the 4th thread */

     if( (rc = pthread_create(  thread + i,
                                pthread_attr_default,
                                (void *) rc_mixer,
                                (void *) (rc_mixer_struct + i)
                             ) ) != 0 ) 
  			{
  			fprintf( stderr, "Cannot create thread %s";, strerror(rc) );
   		exit(1);
   		}
     ...
     }

Monitoring

You need to MONITOR the time that a rc_mixer() is blocked, and keep track when the thread is sleeping (shakes, pours and drinks a rum & coke). A nice way of doing this is to create a separate thread, OR have main() be the monitor thread. The thread should record the time it sleeps and blocks, and the monitoring function should periodically collect blocking and sleeping times. Since, two separate threads accesses these variable you will need to protect the variable via a lock.

You can use either gettimeofday() or time() to keep track of the time.

When the monitor thread is not collecting data, it should sleep.

The monitoring threads should also detect any deadlocks.

Sleep:

The rc_mixer() thread should use sleep() to simulate shaking, pouring and drinking. The sleep should be random and be at most max sleep. Where max sleep is given on the command line.

The monitor() should also sleep periodically. It should sleep when is not monitoring the threads. The DEFAULT period that the monitor should sleep is 10 seconds. i.e., sleep(10).

Output:

A thread should send out status messages on its progress: The message should be printed out to stdout. It should be
in the below format:

 <ingredient_string>_<id_int> : <current_time_int> <status>

A rc_mixer() should denote how long it was blocked and how long it shakes, pours, and drinks (sleeps).

Example:

  COKE_1 :  1 Shakes, pours and drinks for 3 seconds blocked for 2 seconds
  RUM_3  :  6 Shakes, pours and drinks for 3 seconds blocked for 1 seconds

A rc_mixer() should denote the ingredients it is waiting for:

Example:

RUM_3  :  5 Waiting for coke and lime
LIME_5 :  0 Waiting for rum and coke
A rc_mixer() should denote when it is done mixing.

Example:
   COKE_1 :  1 Rum & Coke mixer done
   RUM_3  :  9 Rum & Coke  mixer done
 

The supplier() should denote for how long it was blocked (i.e., waiting for a rc_mixer() to be done).

Example:
   HOLE_6 :  1 Blocked for 1 second
 

The supplier should denote the ingredients it puts out:

Example:
   HOLE_6 :  1 Putting out rum and lime
   HOLE_6 :  5 Putting out coke and lime

The monitor() prints out the status of threads periodically. The status message include the total block time, and total block time per thread (in order of id). The format for this string is:

TOTAL : <current_time_int> Blocktime:
   <total block time all threads>
   [
   <block time thread id 0>
   <block time thread id 1>
   <block time thread id 2>
   ...
   <block time thread id n>
   ]

The last id is the supplier thread. 
Example:
   TOTAL     :  0 Blocktime:    0   [     0     0     0     0 ]

The monitor() also prints out a status if threads deadlock, this message should include the identity of those threads.

In deadlock avoidance a request may be denied. If a request is denied you should print out that the request was denied.

Input Command Line:

rc_mixers -s <number_mixers> -m <max_sleep_time> -d  -i <max_time>

You should use at least 5 flags, 4 of which has optional arguments:

   -s <number_mixers>
   -m <max_sleep_time>
   -i <max_time>
   -r <report period>
   -d
-s sets the rc_mixer() that are launched, -m sets the maximum time a thread sleeps (shake, pour and drinnk), -i sets the maximum time a thread should run, -r sets the time period that the monitor reports blocking time and -d sets whether the thread may deadlock or not.

The default should be not to deadlock (i.e. if the flag is not given on the command line).

You may use additional flags.

Defaults:

Defaults should be set as following: 
   
   -s 3           -- 3 rc mixers
   -m 2           -- max sleep time of 2
   -i 40          -- maximum time mixers are running
   -r 5           -- report period of 5
   -- no d flag, i.e., default is NO deadlock
 

Deadlock:

You will implement a version of the rum and coke problem that deadlocks. You should make it deadlock by manipulating how long threads sleeps and so on. You may want to use additional command line parameters to enable this version to deadlock easier. This version should also run (sometimes) without deadlocking (by changing the sleep time of the command line parameters). Thus there should be a threshold where the program sometimes deadlocks and sometimes runs to completion.

Deadlock Avoidance:

You should also implement a version of the rum & coke problem that avoids deadlocks. This should be an extension of your version that "deadlocks". Your program should print out status messages that indicate how it avoids deadlock.

Minimally, if your program cannot AVOID entering an UNSAFE state, it needs to detect and output that it is in an unsafe state and not in a deadlocked state. If the program can avoid UNSAFE states then your program need to output that it is avoiding entering an UNSAFE state as it occurs.

Libraries:

You need to link to the pthread and real time library (rt) on odin, i.e. you your compile line may look like the below;

gcc -o mixers mixers.o -lpthread -lrt

Submission:

You must submit the following files (i.e., all the files necessary to compile
your project):

   - rc_mixers.c
   - all other files you need *.[ch]
   - Makefile
   - README.txt how you compile and run the program, how to deadlock your deadlock 
           implementation
   - REPORT.txt

To submit the files, use the submit program. For example, to submit the file my_cool_program.c and my_cool_program.h in directory project5 enter

Make sure you have a subdirectory called "project5" before you execute the below command:
   {odin} submit project5 csx730


Report.txt (or .doc)

Please discuss: