CS 4414 Lab 5: Programming with Pthreads and Semaphores

Due 10pm Weds, 3/28

Introduction

pthreads and semaphores

For this assignment, we will be using POSIX threads (pthreads). Although some of the examples and libraries we provide are in C++, the code you write for this assignment should stick with C.

A great tutorial on pthreads can be found here: http://www.llnl.gov/computing/tutorials/pthreads/ We will also be using the POSIX semaphore library. For this, the man page should suffice:

man sem_init

[getting information about semaphores]

And an example:
#include <semaphore.h>

int main(int argc, char** argv) {
  sem_t mySemaphore;
  int pshared = 0; // no process-shared semaphores in LinuxThreads
  int initialValue = 5; // initialize the semaphore to 5
  if (sem_init(&mySemaphore, pshared, initialValue)) {
     // sem_init returned with a nonzero value, so
     // check errno
     switch(errno) {
     case EINVAL:
       fprintf(stderr, "Error: %d out of range for intial value",
	       initialValue);
       break;
     case ENOSYS:
       fprintf(stderr, 
	       "Error: LinxThreads does not support process-shared semaphores");
       break;
     default:
       fprintf(stderr,
	       "Unkown Error Code: %d", errno);
       break;
     }
     exit(errno);
  }
  return 0;
}

[example code for initializing a semaphore]

Linking

If you use pthreads or POSIX semaphores in your code, you must link against the pthread library by adding -lpthread to the compiler options when linking.
gcc semaphore_test.cc -lpthread -o semaphore_test

[how to link with the pthread library]

Synchronization

Ch. 6 of Silberschatz et al. discusses inter-process communication and synchronization.

Submission

Please put your code in your cs4414/lab5 directory in your labunix accounts, and then on Collab submit to confirm the path where we can find your code and the name of your partner. Your documentation and discussion will go in README files that accompany your solutions to each problem. Submit no later than 10:00pm on Wednesday, 3/28.

Warmup Problem i - Producer / Consumer

We have a bounded LIFO buffer (a fixed-size stack) that is initially empty. We then create two threads: the producer, whose job is to write as much as it can to the buffer, and the consumer, whose job is to read as much as it can from the end of the buffer.

There is nothing to submit for this problem, as we have given you two solutions to it in prodcon.zip. One of the solutions uses semaphores and the other uses condition variables.
Note: if running this messes up your console's colors, just type reset and then clear.

Play around with these to make sure that you understand the synchronization concepts. It's always nice to start with working code. Some things to consider:
  1. What are the limitations (if any) of each method?
  2. What changes would you have to make in order to accommodate more than one producer or consumer?

Warmup Problem ii - H2O

You've just been hired by Mother Nature to help her out with the chemical reaction to form water, which she doesn't seem to be able to get right due to synchronization problems. The trick is to get two H atoms and one O atom all together at the same time. The atoms are threads. Each H atom invokes a procedure H() when it's ready to react, and each O atom invokes a procedure O() when it's ready. For this problem, you are to write the code for H() and O() (and of course, all the other code to make your program work). The procedures must delay until there are two H atoms and one O atom present. When this happens, all three threads must print out a message saying that Water has been created, and then exit. You may use either semaphores or locks and condition variables for synchronization, but avoid starvation and busy waiting

There is nothing to submit for this problem, as we have given you a semaphores solution to it in h2o.zip. This solution is probably not the most elegant solution, and is by far not the only solution. If you come up with a better solution, or one that uses condition variables, please email the TA's with it.

Problem 1 - Dining Philosophers

Philosophers Starting Position
As in the classic problem, there are n philosophers sitting around a table, and they only have n chopsticks, placed evenly between them. Each philosopher follows the exact same steps:
do forever {
  THINK for a while
  get HUNGRY
  pickup left chopstick
  pickup right chopstick
  EAT
  put down left chopstick
  put down right chopstick
}

[pseudo-code for philosopher]


Details

We have provided skeleton code for you in philosophers.zip with a naive implementation of this algorithm. It is your task to implement this algorithm, free of deadlocks and starvation.
Hint: most of your code will go in philosopher_thread() Please also note that it is better to turn in a solution that works, even if it may suffer from starvation, rather than nothing at all.

Pseudocode solutions to this problem, using both semaphores and monitors, have been given in class. You must use mutexes and condition variables for your solution. Also, each philosopher must execute the exact same algorithm (no asymmetric solutions). The philosopher's id is only to be used for debugging purposes, and may not be part of the solution.

philosophers_stats

You will also notice that we have provided philosophers_stats.cc for you. This program will take the output from the philosophers program, and calculate some simple statistics from it. Feel free to modify this program in any way - it is not part of the submission, but rather a utility program to help you debug and test your code. Just be sure that your final solution runs with the version we're giving you.
  ./philosophers 5 0.5 1 > out.txt
  ./philosophers_stats out.txt

[running philosophers_stats on a file]
(try ./philosophers 5 0.1 1 | tee out.txt)
  ./philosophers 5 0.5 1 | ./philosophers_stats -o

[piping the output of philosophers
directly to philosophers_stats, 
and also showing the output to stdout (-o)]

What To Submit

You should submit all the source files necessary to compile philosophers (including the Makefile - make sure that a single Makefile covers all the code you submit). In your README file, include any compilation instructions, a brief description of your philosopher algorithm, an explanation for why it is deadlock and starvation free, and anything else you would like us to know. Finally, note that our skeleton code provides a drop-in replacement for printf, called LOCKED_PRINTF, that acquires a mutex for stdout, calls printf, flushes stdout, and releases the mutex, to avoid garbled output from concurrent threads.

Problem 2 - Matchmaker

In what has become another idiosyncratic classic computer science synchronization problem, you have been hired by an environmental agency to help improve the whale population. Because unscrupulous commercial interests have dangerously lowered the whale population, whales are having synchronization problems in finding a mate. The trick is that in order to have children, three whales are needed, one male, one female, and one to play matchmaker - literally, to push the other two whales together (the truth of this claim is unsubstantiated as far as we know). Your job is to write the two procedures Male() and Female(). Each whale is represented by a separate thread. A male whale calls Male(), which will either act as a matchmaker or wait for a female and a matchmaker. A female acts similarly.

Details

Your implementation for this problem must use Semaphores. You may also find it useful to have a mutex for critical sections. Printing to the console should be part of a critical section.

We have provided skeleton code in whales.zip.

When whales mate, they leave the problem (they no longer participate in mating or matchmaking). When a new whale is created, if it can act as a matchmaker (i.e., a male and female are already waiting), it must do so, but if a matchmaker is not needed at this time, it should wait for a mate. And once it starts waiting for a mate, it should no longer act as a matchmaker. For whales that serve as a matchmaker, after completing a match, they should check again to see if a matchmaker is needed.

For example, consider the following scenario:
(t=1.0) 0: Female Created
(t=2.0) 1: Female Created
(t=3.0) 2: Male Created
(t=3.0) 0: Found Mate
(t=3.0) 2: Found Mate


Here Whales 0 and 2 have found mates and need a matchmaker.  It 
would make sense for Whale 1 to now act as a matchmaker, but
because she arrived before Whale 2 and already started waiting for a male, she cannot act as a matchmaker,
leaving Whales 0 and 2 still waiting for a matchmaker to appear.

[whales example]

What To Submit

You should submit all the source files necessary to compile whales (make sure your Makefile covers all programs you submit). In your README file, include any compilation instructions, a brief description of your Male and Female algorithms, an explanation for why it is deadlock and starvation free, and anything else you would like us to know. This skeleton code also provides LOCKED_PRINTF.

The whale problem is courtesy of Sang Son.
Copyright Kevin Skadron, 2005, 2006, 2008.
Last revised Mar. 20, 2012.