CSCI 3000
Spring 2017
Programming Assignment 2

Due: Friday, March 3, 11:59pm

The assignment

This assignment is to implement a simple producer/consumer program using POSIX threads in C. You are to design the threads to run with a preemptive scheduler, and to communicate through a shared bounded buffer as shown in the book. The shared buffer should be an array with 10 slots.

Use shared counters to hold the number of occupied cells, the next location for the producer to insert a value and the next location for the consumer to remove a value. Also keep a shared boolean variable, 'consumer_finished', which should be 0 when the consumer is running and 1 when the consumer has stopped.

The producer should send a sequence of integers to the consumer. Specifically, it should send the sequence 1, 2, 3, …, 1000000, in that order. It does that by storing the numbers into the buffer in a circular way, wrapping around to index 0 when it hits the end of the buffer. The producer should keep a local variable telling the next index in the shared buffer to store into.

The producer must not try to store anything into a full buffer. If the buffer is full, the producer should check if consumer_finished is 1. If so, the producer should stop. Otherwise, it should wait for the consumer to remove something.

The consumer should remove numbers from the shared buffer in the same order in which they were put into the buffer by the producer. It also wraps around at the end of the buffer. The consumer will need a local variable holding the next index to remove something from.

The consumer should just check that it has indeed received exactly the sequence 1, 2, 3, …, 1000000, in the correct order. If the sequence that it gets is incorrect, the consumer should show the first bad number. Then it should set consumer_finished to 1 and stop.

When the consumer receives 1000000, it should print a message that all went well and stop.

The main function must create the two threads then join with each. Be sure to do the join, since otherwise main will return and the process will be destroyed before the producer and consumer have time to do much of anything.

Consider adding trace prints to the consumer and producer showing what they are writing. But be cautious. You will want to use a mutex to protect the trace prints, since otherwise they can be interleaved at the character level.

Variations on a theme

Provide three different programs, called pc1.c, pc2.c and pc3.c.

  1. (pc1.c) Implement the buffer in a naive way, without any synchronization. Both threads will update the counter telling how many slots in the buffer are occupied. This program should encounter an error. If it doesn't, try running it again.

    If the producer finds that the buffer is full, it should busy-wait until the buffer is not full. If the consumer finds that the buffer is empty, it should busy-wait until the buffer is not empty.

  2. (pc2.c) Implement the buffer using a mutex to protect the critical section where the shared counter is updated.

    If the buffer is full and the consumer has not finished, the producer should wait on condition variable 'full'. When the producer inserts a value into an empty buffer, it must signal condition variable 'empty'.

    Similarly, if the buffer is empty, the consumer should wait on a condition variable 'empty'. When the consumer removes a value from a full buffer, it must signal 'full'.

  3. Implement the shared counter as a wait-free shared counter, which you can implement using two variables, p_counter and c_counter. Only the producer modifies p_counter. Each time the producer inserts something into the buffer, it adds 1 to p_counter. Only the consumer modifies c_counter. Each time the consumer removes something from the buffer, it subtracts 1 from c_counter. (So c_counter will become negative.) The value of the counter, which tells how many items are in the buffer, is p_counter + c_counter.

    Do not provide any synchronization. Make the producer busy-wait when the buffer is full. Make the consumer busy-wait when the buffer is empty.

    Why don't you need any synchronization? Look at it from the producer's perspective. It fetches the value of c_counter. If c_counter is being modified by the consumer, the producer either gets the value just before modification or just after modification. Either way, it gets a snapshot of c_counter at some point in time. Since only the producer modifies p_counter, it does not need to worry that p_counter might be changed.

    The picture from the consumer's point of view is similar. It gets a snapshot of p_counter.

    So, by splitting the count into two variables, we have achieved a shared counter without waiting.

Programs pc2.c and pc3.c should work correctly. Test them to make sure. The consumer should report that all went well.

Turn in all three solutions. (So don't destroy one to build the next one. Make a copy instead.)

Compiling POSIX thread programs

To compile pc1.c, use the following command.

gcc -Wall -W -o pc1 p1.c -lpthread

Adding -lpthread is essential. If you don't, for reasons that I do not understand, the compiler will use stubs for the pthread functions. That is, the pthread functions will not do anything. Option -lpthread tells the compiler to link in the pthread library.

Asking questions and submitting

To ask a question, submit your current work using the following command.

~abrahamsonk/3000/bin/submit q2 files

Then send me an email stating your questions about your program.

Submitting your work

Log into xlogin.cs.ecu.edu. Make sure your current working directory contains your program. Do the following command.

~abrahamsonk/3000/bin/submit 2 pc1.c pc2.c pc3.c