Answer to Question 3 -- 20 points

There are, of course, several ways to answer this question. Mine was to have one monitor and two condition variables -- one for sleeping readers and one for sleeping writers. I keep track of the number of threads reading at any one time, and the number of threads writing at any one time (which of course will be either zero or one). I also keep track of the number of readers that are sleeping because a writer is writing the data, and the number of writers that are sleeping because any other thread is accessing the data.

begin_reading() and begin_writing() both enter the monitor and then check to see if they should sleep. If they do, they increment the proper sleeping variable, and wait on the proper condition variable. This is done in a while loop that will not be exited unless the proper conditions hold for reading/writing.

When the while loop is exited, nreaders or nwriters is incremented, the monitor is released, and the procedures exit. Additionally, begin_reading() calls cv_notify() on the reading condition variable just before it exits if there are any more sleeping readers. This is because multiple readers may access the data at once.

end_reading() decrements nreaders and if it is the last reader, it wakes up any writing threads that are sleeping. end_writing decrements nwriters and wakes up readers or writers if there are any sleeping. Notice it does this in preference to readers. It also only wakes up one reader. It assumes that that reader will wake up the rest in begin_reading().

mon_t mon;
cv_t read_cv;
cv_t write_cv;
int sleeping_readers;
int sleeping_writers;
int nreaders;
int nwriters;

initialize()
{
  mon = mon_create();
  read_cv = cv_create(mon);
  write_cv = cv_create(mon);
  sleeping_readers = 0;
  sleeping_writers = 0;
  nreaders = 0;
  nwriters = 0;
}

begin_reading()
{
  mon_enter(mon);
  while(nwriters > 0) {
    sleeping_readers++;
    cv_wait(read_cv);
    sleeping_readers--
  }
  nreaders++;
  if (sleeping_readers > 0) cv_notify(read_cv); 
  mon_exit(mon);
}

end_reading()
{
  mon_enter(mon);
  nreaders--;
  if (nreaders == 0) {
    cv_notify(write_cv);
  }
  mon_exit(mon);
}
   
begin_writing()
{
  mon_enter(mon);
  while (nreaders > 0 || nwriters > 0) {
    sleeping_writers++;
    cv_wait(write_cv);
    sleeping_writers--
  }
  nwriters++;
  mon_exit(mon);
}

end_writing()
{
  mon_enter(mon);
  if (sleeping_readers > 0) {
    cv_notify(read_cv);
  } else if (sleeping_writers > 0) {
    cv_notify(write_cv);
  }
  mon_exit(mon);
}

Part 2

This is very simple to do with general semaphores: Add a gsem called g. In initialize() do g = gsem_create(10). In the beginning of begin_reading() do gsem_P(g), and at the end of end_reading() do gsem_V(g).

Part 3

If the monitor/semaphore system does not prevent starvation, then obviously anything can starve. Otherwise, writers can certainly starve because as readers come into the system, if there is a thread reading the data, then the new reader will be able to read, further shutting out the writer. Also, the code favors readers over writers in the the end_writing() routine. One way to prevent starvation is to have readers wait on read_cv if sleeping_writers is greater than zero. Moreover, you may want to alternate who gets preference in end_writing() with something like:

static int rw = 0;

end_writing()
{
  mon_enter(mon);
  if (sleeping_readers > 0 && sleeping_writers > 0 && rw) {
    cv_notify(read_cv);
    rw = !rw;
  } else if (sleeping_readers > 0 && sleeping_writers> 0 ) {
    cv_notify(write_cv);
    rw = !rw;
  } else if (sleeping_readers > 0) {
    cv_notify(read_cv);
  } else if (sleeping_writers > 0) {
    cv_notify(write_cv);
  }
  mon_exit(mon);
}