// CSI400 Fall 2004 Project 2 DUE due 10/27
//
//
// "Threader" sample program:
//   Introduction to pthreads programming and experimentation.
// 
// The experiments are designed to let you observe why mutual exclusion 
// is necessary for concurrent programming.
//
// First, put a copy of threader.C and Makefile in a new directory and
// verify that "(g)make" builds the threader executable.  Then, follow
// the directions for the "ANALYSIS" and "EXPERIMENTs" below and write
// the requested explantions into a file named Pr2Report, 
// to be submitted electronically to project Pr2
// with copies of the corresponding modified threader.C files.
//
// Pr2Report have a filename "extension" to indicate
// the format of a file: .txt, .html, .pdf, .ps, or .doc
// 
//

#include <iostream.h>
#include <pthread.h>
#include <string.h>
#include <stdlib.h>

void * loopfun( void *param );  // function run by each new thread
void  increment_bad_counter();
void  increment_good_counter();

// Under Linux, output printing errors due to races can be observed when
// use of cout is not protected by a lock.  I did not observe such
// errors under Solaris.  Perhaps iostream built for Solaris is more
// thread safe than iostream for Linux/Redhat 5.1

// How to run threader:
//  threader           (do protect cin/cout)
//  threader -no-lock-io  (do not protect cin/cout)
//   now, to start a new thread, type any letter, ENTER,
//   a decimal number (10000000-10000000 are useful values), ENTER.
//   Repeat this input to start additional threads.

int lock_io = 1;  //TRUE iff each io operation will be protected by
                  //the mutex below.
pthread_mutex_t io_mutex = PTHREAD_MUTEX_INITIALIZER;

// ANALYSIS(1):
// Find where all the 14 variables listed below are declared:
// lock_io, io_mutex, good_counter_mutex, good_counter, bad_counter, argc,
// argv, my_attributes, inputnum, CHIN, tid, ret, mythread, and countdown.
//
// For each variable, first record whether it is STATIC (allocated once
// and for all during the lifetime of the process) or AUTOMATIC (each 
// instance is allocated on the thread's stack in an activation record
// associated with some function activation).
//
// Second, figure out which variables have only one instance during
// a run of the program (which includes many threads created after user
// input), and which have a separate instance for each new thread.
//
// Write the answers in the Report.

// EXPERIMENT(1):
// To make threader start a new thread, 
//   First, type any letter.
//   Second, press the enter key.
//   Third, type the countdown value and press enter.
// Useful countdown values range from 10 Million to 100 Million.
// Do this:
//   Observe that threader -no-cin-lock doesn't "stop" for you to type in
//    the number after you press the enter key the first time.
//   Figure out why..and type your explanation into your Report.
// ANALYSIS(2): Suppose you are typing the inputs to command threader
//   to create 3 new threads.  That requires 6 lines of input.
//   Suppose you finished typing 5 of those input lines and
//   the system is waiting for the 6th.  At this time, answer: 
//   Which thread(s) are running and which thread(s) are
//   blocked, and where are they blocked?

// We also demonstrate the effect of races on an unprotected
//  shared counter variable.  The errors are observable because
//  we maintain a protected shared counter also.  
// EXPERIMENT(2):  remove the protection from "good_counter" and
//  try to observe:
//   (1) Changes in program speed (performance).
//   (2) Do the two counters consistant with one another?
//  Create a suitably renamed copy of your modified threader.C and report:
//   (1) The performance changes you observed.  The maximum 
//       credit will be given for a QUANTITATIVE report that
//       includes a description of the computer and operating
//       system you experimented on.
//   (2) Where the two unprotected counters consistant (i.e. equal)?
//       How much did they diverge?

// EXPERIMENT(3):  Restore the mutual exclusion protection for good_counter.
//  Add code to print, in addition to the two counter values, the 
//  "discrepancy rate" expressed as a percentage:
//
//                      bad_counter - good_counter
//   discrepancy rate = -------------------------- * 100.0
//                             good_counter
//
//  To prevent "loss of precision", program the calculation as follows:
//   (1) do the subtraction with long signed integers.
//   (2) convert the subtraction result to a float.
//   (3) do the division, the multiplication and result
//       printing with float type values.
//
//  Put in your Report how the discrepancy rate varies with the number of
//  contending threads and with the length of time you let it run.  Is 
//  the rate always negative?  For different numbers of threads, does 
//  the rate change as time goes on?
//  Optional:  Note that my code does not bother to protect good_counter
//  when it is being accessed to be printed.  See if adding protection
//  there will make a difference.
//

// EXPERIMENT(4): Find out and write a summary about the "volatile" keyword
// in C/C++.  
// Run the following command and study the last page or so of its output:
// /usr/local/binutils/objdump --disassemble --reloc --source threader.o 
// Locate and copy into your report the assembly language/machine language
// instructions that (1) increment bad_counter 
// (2) call pthread_mutex_lock (3) increment good_counter and
// (4) call pthread_mutex_unlock
//
// OPTIONAL:  On a recent Linux system, compare the behavior of threader
// with that of a variant in which the "volatile" keywords on the declarations
// of good_counter and bad_counter are removed.  Try to account for the 
// difference.  Try to use "objdump --disassemble --reloc --source threader.o"
// "objdump --disassemble threader", or g++ with the -S flag, to find
// out what caused the difference of behavior.

pthread_mutex_t good_counter_mutex = PTHREAD_MUTEX_INITIALIZER;
volatile long unsigned int good_counter = 0;
volatile long unsigned int bad_counter = 0;

int main(int argc, char *argv[] )
{
  // I need this to override one or two default attribute values.
  pthread_attr_t my_attributes;

  char CHIN;

  // Initialize the attributes structure that will be passed 
  //  to the function that creates new threads.
  pthread_attr_init( & my_attributes );

  // Detached here means that some other thread does NOT have to ask the
  //  new thread to "join" before the system reclaims the new thread's 
  //  resources when the new thread exits.
  pthread_attr_setdetachstate( & my_attributes, PTHREAD_CREATE_DETACHED );

  // The following is not needed on Linux because the the Linux kernel
  // handles all scheduling of POSIX threads.  The Solaris "process scope"
  // threads are not rescheduled by the kernel periodically.  On Solaris,
  // the interleaving of thread execution is not so easily observed when
  // "process scope" scheduling is used.  Process scope scheduling is
  // said to be more efficient.

  // Preprocessor conditionals like this are often used to vary the source
  // code according to computer or operating brand or version.
  // Preprocessor macro "sun" is predefined in compilations done on sun systems

#ifdef sun
  pthread_attr_setscope( & my_attributes, PTHREAD_SCOPE_SYSTEM );
#endif

  argc--; //Count only command line args AFTER the command.
  argv++; //Point to command line arg C-strings AFTER the command.
  while( argc != 0 )
    {
      if( (!strcmp(argv[0],"-no-lock-io")))
	{
	  lock_io = 0;
	  argc--;
	  argv++;
	  continue;  //got a good command line argument.
	}
      cerr << "Invalid command line argument(s)." << endl;
      exit(1);
    }
		

  long unsigned int inputnum;
  while( cin >> CHIN )
    {
      if(lock_io)
	pthread_mutex_lock(&io_mutex);
      cin >> inputnum;
      if(lock_io)
	pthread_mutex_unlock(&io_mutex);

      pthread_t tid;
      int ret;

      ret = pthread_create( &tid, &my_attributes, loopfun,
			    reinterpret_cast<void *> (inputnum));
    }
  exit (0);
}

void * loopfun( void *param ) 
{
  pthread_t mythread;
  pthread_detach( mythread = pthread_self() );

  if(lock_io)
    {
      pthread_mutex_lock( & io_mutex );
    }
  cout << "Thread " << mythread << " starting.." << endl;
  if(lock_io)
    {
      pthread_mutex_unlock( & io_mutex );
    }

  long unsigned int countdown = 0;

  countdown = reinterpret_cast<long unsigned int>(param);

  for( ; ; )
    {
      if(lock_io)
	{
	  pthread_mutex_lock( & io_mutex );
	}
      cout << "Thread " << mythread 
	   << " count " << countdown << " reset." 
	   << " Good=" << good_counter 
	   << " Bad=" << bad_counter
	   << endl;
      if(lock_io)
	{
	  pthread_mutex_unlock( & io_mutex );
	}

      while( countdown )
	{
	  increment_bad_counter();
	  increment_good_counter();
	  countdown--;
	}

      countdown = reinterpret_cast<long unsigned int>(param);
    }
  return 0;
}

void  increment_bad_counter()
{
  bad_counter = bad_counter + 1;
}

void  increment_good_counter()
{
  pthread_mutex_lock( & good_counter_mutex );
  good_counter = good_counter + 1;
  pthread_mutex_unlock( & good_counter_mutex );
}

