Semaphores Lab

Overview

In this lab you will get hands-on practice with POSIX semaphores by solving three classic synchronization problems. The lab has three parts:

  1. Producer / Consumer (Bounded Buffer) – the running example from lecture and from the assigned reading.
  2. Thread-safe Ordered Logging – forcing N threads to print in a fixed sequence using only semaphores.
  3. Chemical Reaction – the H2O assembly-line problem from the earlier threading lab, but reimplemented with semaphores instead of locks and condition variables.

Across all three parts you may use only POSIX semaphores (sem_init, sem_wait, sem_post, sem_destroy) for synchronization. No pthread_mutex_*, no condition variables, and, no busy-waiting.

The total assignment is worth 100 points: 30 for Part 1, 30 for Part 2, and 40 for Part 3.


Getting the starter code

Download and untar the starter code with the commands below

cd ~/cs224
wget https://cs224.cs.vassar.edu/labs/semaphores.tar
tar xvf semaphores.tar
cd semaphore_lab

The starter code is laid out as follows:

semaphore_lab/
  Makefile                    # builds everything; "make run" runs all tests
  part1/
    producer_consumer.c       # YOU EDIT THIS
    test_producer_consumer.c  # provided test harness
    Makefile
  part2/
    logger.c                  # YOU EDIT THIS
    test_logger.c             # provided test harness
    Makefile
  part3/
    reaction.h                # do not modify
    reaction.c                # YOU EDIT THIS
    test_reaction.c           # provided test harness
    Makefile

From inside semaphore_lab/ directory, make builds every part and make run runs every test suite. You can also cd partN && make run to focus on a single part.


Part 1: Producer / Consumer (30 points)

Implement the bounded-buffer producer/consumer solution from the lecture and the reading. The starter file part1/producer_consumer.c already takes care of:

What’s left for you is the synchronization logic: declare and initialize the semaphores, fill in put() and get(), and destroy the semaphores at the end. The exact spots are marked with TODO (Part 1) comments in the starter.

You will need three semaphores:

Building and running

cd part1
make            # builds producer_consumer and test_producer_consumer
make run        # runs the test suite

The harness exercises your code with several producer/consumer counts and item counts. It checks that the right total number of items is produced and consumed, that no item is dropped or duplicated (the sum of all consumed values has to match the expected mathematical sum), and that the program does not deadlock.

Deliverable for Part 1

part1/producer_consumer.c with your edits.


Part 2: Thread-safe Ordered Logging (30 points)

You have N threads, each representing a process that generates a log message. Each thread has an assigned sequence number from 0 to N-1. Your job is to:

The starter file part2/logger.c already declares an array sem_t sems[NUM_THREADS], creates the threads (in reverse order, on purpose, so that out-of-order printing is exposed quickly), and joins them. Your job is to:

  1. initialize the semaphores in main(),
  2. fill in logger_thread() so each thread waits its turn before printing, then hands off to the next thread,
  3. destroy the semaphores at the end.

Hints for Part 2

Read these one at a time – only peek at the next hint if you’re truly stuck.

Building and running

cd part2
make
make run        # runs ./logger 20 times, verifying ordering each run

Because a buggy solution can sometimes get lucky, the test harness runs your program many times; any single bad run fails the suite.

Deliverable for Part 2

part2/logger.c with your edits.


Part 3: Chemical Reaction (40 points)

Mother Nature is back. She still wants water, but she has switched synchronization libraries: she now wants the H2O assembly line implemented purely with POSIX semaphores. The setup is the same as in Problem 2 of the earlier threading lab:

You will edit part3/reaction.c. The header part3/reaction.h declares struct reaction (which already has slots for a mutex, two queue semaphores, and the H/O counters; you may add more fields if you want) and the prototypes you must implement:

void reaction_init(struct reaction *r);
void reaction_destroy(struct reaction *r);
void reaction_h(struct reaction *r);
void reaction_o(struct reaction *r);

make_water() is provided by the test harness and must be called exactly once per (2H + 1O) group, while those calls are still active.

Hints for Part 3

Building and running

cd part3
make
make run

The test harness creates many H and O threads and verifies that:

Deliverable for Part 3

part3/reaction.c. (You may also submit reaction.h if you added fields, but you must not change the public interface.)


Submitting the lab

From the lab/ directory, run:

make submit

This produces semaphores-submit.tar containing your edited source files. Upload that tarfile to Gradescope. The autograder there runs the same test suites as make run, plus additional stress and correctness tests, and will assign 30 / 30 / 40 points across the three parts.


Grading

Part Points
Part 1 – Producer/Consumer 30
Part 2 – Ordered Logger 30
Part 3 – Chemical Reaction 40
Total 100

Each part is graded by the test harness for that part. Within a part, points are awarded proportionally to the test cases your solution passes. As with prior threading labs, simplicity counts: a clean, short, correct solution is much more valuable than a long, fragile one. Spend the time to get the synchronization right rather than trying to paper over a race with extra sem_post or sem_wait calls.