SOURCE FILE: worker.cpp



/*     
In this program a boss process puts a series of 'work pieces' into
a buffer, one after the other.  There are three worker threads
that process each work piece.  Worker #i performs task #i.

Each worker needs to have exclusive access to the work piece 
while processing it.  Each worker must do its task exactly once 
on each work piece, but there's no restriction on the order in which 
the workers do their processing -- workers 0, 1, and 2 are 
permitted to do their tasks in the order 0,1,2 or 0,2,1 or 1,0,2 
or 1,2,0 or 2,0,1 or 2,1,0.

Think of each worker as being busy doing lots of other 
things much of the time. The program simulates this with random delays.

After all three workers have processed the work piece, the last worker 
to process it has to pass it along to a 'Finisher' thread for 
final processing.

==============================

The threads implementing the Boss, Workers, and Finisher have to 
coordinate and synchronize their actions.  Their goals are to

1. avoid using busy waiting to coordinate/synchronize,

2. achieve as much parallel processing as possible,

3. avoid the possibility that a worker could process the same work
   piece twice,

4. avoid the possiblity that a worker could process a nonexistent work
   piece.

5. avoid the possibility that a worker could overwrite a buffer 
   before all the processing on its contents has been completed,

6. make sure all the work gets finished, and

7. make sure that the only threads that are involved in
  regulating access to a buffer are the threads that either a)
  currently are using the buffer, b) currently are trying to get
  access to the buffer (in entry code), or c) currently are
  releasing the buffer (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 <cmath>
#include "sem.h"

using namespace std ;

/* ######################################## */
/*             Global Variables             */
/* ######################################## */

const int numStations = 2 ; 
const int numWorkers = 3 ;
const int maxJobNames = 10 ;
const int numJobNames = 5 ;
const int WORK = 0 ;
const int FINISH = 1 ;

     /* When a buffer[i]==j it means that work piece #j is in buffer[j].*/
/* Dec 18, 2016: Correction of typo above: */
     /* When a buffer[i]==j it means that work piece #j is in buffer[i].*/

int buffer[numStations] ;

     /* When numTasks_left==k, it means k workers still need to 
        perform their task on the work piece in buffer[0].
        After they do that, the last one gives the work piece
        to the Finisher. */
int numTasks_left ;

     /* jobName[j] is the name of work piece #j */
string jobName[maxJobNames];

     /* child_t are global variables to represent the
        dynamically-created threads. */
pthread_t worker_t[numWorkers] ;
pthread_t boss_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 ;

     /* A data type - a struct (class) with an int field to represent
        a thread ID. */
struct threadIdType
{
  int id ;
};

/* =========================================================== */
/*   In this area, declare whatever semaphores and other 
     variables you want to use for synchronization */
/* =========================================================== */




/* ################################################## */
/*                         init                       */
/* ################################################## */
void init() 
{
   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) ;}

        /* Give some mnemonic names to the work pieces.  The first name 
           is used for an empty buffer. This is coded so no changes are 
           needed here as long as the value of "numJobNames" 
           remains the same (10). */
   jobName[0]="No Work Piece";
   jobName[1]="Work Piece #1" ;
   jobName[2]="Work Piece #2" ;
   jobName[3]="Work Piece #3" ;
   jobName[4]="Work Piece #4" ;
   jobName[5]="Work Piece #5" ;
   jobName[6]="Work Piece #6" ;
   jobName[7]="Work Piece #7" ;
   jobName[8]="Work Piece #8" ;
   jobName[9]="Work Piece #9" ;

/* =========================================================== */
/*   In this area, initialize the semaphores and other 
     variables you will use for synchronization */
/* =========================================================== */







}

/* ################################################## */
/*                    DelayAsMuchAs                   */
/* ################################################## */
void delayAsMuchAs (int limit)
{
 int time, step;
 time=(int)random()%limit;
  // cout << "Time is: " << time << endl ; // to check on randomness
 for (step=0;step<time;step++) sched_yield() ;
}

/* ################################################## */
/*                       Boss                       */
/* ################################################## */
/* The mother thread spawns a child thread that executes this function.  
   This function carries out the role of the boss, assigning
   work pieces one at a time. */

void * Boss(void * ignore)
{
   int i, j, delayLimit=100 ;
   for (i=1; i<numJobNames; i++)
   {
         /* I delay a random time to simulate the time it takes me
            to get ready to place another work piece in the buffer.*/
      delayAsMuchAs(delayLimit);

         /* When the buffer is available, 
            I place the work piece on the buffer. */

         /* THIS IS A PLACE FOR SYNCHRONIZING. */




      buffer[WORK] = i; /* put job #i onto buffer[WORK]. */
      numTasks_left = numWorkers; /* one task remains for each worker. */
      pthread_mutex_lock(&stdoutLock) ;
      cout << "Boss places " << jobName[buffer[WORK]] 
           << " in buffer #0." << endl ;
      pthread_mutex_unlock(&stdoutLock);

         /* THIS IS A PLACE FOR SYNCHRONIZING. */




   }
   pthread_exit ((void *)0) ;
}

/* ################################################## */
/*                         Worker                      */
/* ################################################## */
/* The mother thread spawns child threads deignated
   as workers.  Each of those children executes this
  function - with a different id value in the parameter. */

void * Worker(void * idPtr)
{
       /* Declare some some local variables (these are not
          shared).  Type cast the parameter to recover "me"
          -- the unique serial number of this particular worker. */
   int me = ((threadIdType *) (idPtr))->id ;
   int i, j;
   
          /* Try this larger delay to test after adding the 
             synchronizing code. */
   /* int delayLimit=100000 ;  */
   int delayLimit=100 ;

   bool I_pass = false ;

   for (i=1; i<numJobNames; i++)
   {
         /* I delay a random time.  This simulates 'other
            work' that the worker may have to perform
            before getting started on the current work piece.*/
   delayAsMuchAs(delayLimit);

         /* Here is a diagram of the problem set-up.

                              Boss
                              buffer

                  Worker #1  Worker #2  Worker #3
   
                             Finisher
*/
         /* THIS IS A PLACE FOR SYNCHRONIZING. */




     /* I declare what I am doing */
   pthread_mutex_lock(&stdoutLock) ;
   cout << "Worker number #" << me << " processes " ;
   cout << jobName[buffer[WORK]] << "." << endl ;
   pthread_mutex_unlock(&stdoutLock);

	/* I delay a random time to simulate the time it takes for me to
	   perform the work. */
   delayAsMuchAs(delayLimit);

   numTasks_left-- ;

       /* I have to give the work piece to the Finisher if 
          I am the last worker to process it. */
   I_pass = (0==numTasks_left) ; 

         /* THIS IS A PLACE FOR SYNCHRONIZING. */




       /* If I'm supposed to give the work piece to the Finisher */
   if (I_pass) 
   {
         /* THIS IS A PLACE FOR SYNCHRONIZING. */



          /* Tell the world what I'm doing. */
     pthread_mutex_lock(&stdoutLock) ;
     cout << "Worker number #"<< me << " gives " 
          << jobName[buffer[WORK]] << " to the Finisher." << endl ;
     pthread_mutex_unlock(&stdoutLock);

          /* transfer the work piece to the next buffer. */
     buffer[FINISH]=buffer[WORK] ;

         /* make buffer[WORK] empty */
     buffer[WORK]=0;

         /* THIS IS A PLACE FOR SYNCHRONIZING. */





   } /* end of actions performed by the passer */
 } /* end of main for-loop  */
   pthread_exit ((void *)0) ;
} /* end of Worker function */

/* ################################################## */
/*                       Finisher                       */
/* ################################################## */
/*  The mother thread spawns child threads and then executes this
    function.  This is convenient because this function should
    be the last to exit.  This function plays the role of 
    the Finisher. */

void * Finisher (void * ignore)
{
   int i, j, delayLimit=1000 ;
   for (i=1; i<numJobNames; i++)
   {
          /* I delay a random time to simulate whatever work I need
             to do before being ready to process another buffer. */
      delayAsMuchAs(delayLimit);

         /* THIS IS A PLACE FOR SYNCHRONIZING. */




      pthread_mutex_lock(&stdoutLock) ;
      cout << "Finisher removes "
           << jobName[buffer[FINISH]] << " from buffer #1." << endl ;
      pthread_mutex_unlock(&stdoutLock);

      buffer[FINISH]=0; // remove the job.

         /* THIS IS A PLACE FOR SYNCHRONIZING. */



   }
   return (void *) 0;
}

/* ################################################## */
/*                         Main                       */
/* ################################################## */
int main() 
{ 
   init(); 
   cout << endl;
   cout << "Welcome to the work site!" << endl ;
   cout << endl;

       /* This is a pointer to a struct (class) that contains an int
          field - it is a convenient data type to use as the parameter
          to the child function.  */
   threadIdType * idPtr ; 

   int i, ignore=0;

   for (i=0; i<numWorkers; i++)
   {
      idPtr = new threadIdType ; /* allocate memory for struct */
      idPtr->id = i ;  /* records current index as the child's ID */

         /* The call below is what actually creates the child thread
            and passes a pointer to the struct 'idPtr' as the
            parameter to the child function. */

      if (0!=pthread_create(&worker_t[i], NULL, Worker, (void *) idPtr))
         {cout << "THREAD CREATION FAILURE!" << endl; exit(-1) ;}

      if ( 0!=pthread_detach(worker_t[i] ))
         {cout << "THREAD DETACHMENT FAILURE!" << endl ; exit(-1) ;}
   }

   idPtr = new threadIdType ; /* allocate memory for struct */
   idPtr->id = ignore ;  /* sets id to 'whatever' */

   if (0!=pthread_create(&boss_t, NULL, Boss, (void *) idPtr))
       {cout << "THREAD CREATION FAILURE!" << endl; exit(-1) ;}

   if (0!=pthread_detach(boss_t))
       {cout << "THREAD DETACHMENT FAILURE!" << endl ; exit(-1) ;}
	
 Finisher((void *)0) ;

 cout << endl;
 cout << "Thank you for your visit!" << endl ;
 cout << endl;

 return 0 ;
}