Overview
In this lab we will:
- Demonstrate the problems which may occur when mutual exclusion is not properly enforced across critical sections in multithreaded applications
- Make a naive attempt to enforce mutual exclusion using a guard variable
- Enforce mutual exclusion properly using a semaphore
A. The problem with threads
We take a look a look at what can go wrong when we do not enforce mutual exclusion across critical sections. We will again use the pthread library to create our threads. Take a copy of problem.c which creates two threads using pthread_create. One of them, the producer, puts elements into count by incrementing it. It also increments a variable representing the number of elements it has put into count. This variable is called in. The consumer thread takes elements out of count by decrementing it. It also increments a variable representing the number of elements it has taken out of count. This variable is called out. Both threads update a shared variable, count, and those sections of code that do so are therefore critical sections. Mutual exclusion is currently not enforced across these critical sections. We might expect the number we have put in minus the number we have taken out to give us the current value of count i.e.
- in - out = count or
- in - out - count = 0
Compile the program as shown below and run it. In the code we print out the second value above every 5 seconds. Is it always zero? Why not?
$ gcc -o problem problem.c -lpthread
B. Fixing the problem: Attempt #01
We have seen that we need to enforce mutual exclusion across our two critical sections. As a first attempt, add a global guard variable to the code. Initialise it to 1 in its declaration. For a thread to enter a critical section the guard must have a value of 1. The thread should decrement the guard on entering and increment the guard on leaving a critical section. Hopefully this will keep other threads out while a thread is in a critical section. Does this solve the problem? Why not?
C. Fixing the problem: Attempt #02
To fix the problem properly we need to treat the sections of code which update shared variables as mutually exclusive critical sections. We can enforce such program behaviour with the help of a semaphore. You can think of a semaphore as a lock that must be acquired on entering a critical section and released on exiting a critical section. The call to sem_wait acquires the lock and the call to sem_post releases the lock. Any thread attempting to acquire an already held semaphore must wait until it is released before it can acquire it and proceed.
Below you will find the C code and function calls which you will need to add at various points in the program. (Note that a semaphore should be initialised and destroyed only once.) Compile the program and run it. Has the problem been solved? It should have been. What are the crucial differences between a guard variable and a semaphore which mean that a semaphore can be used to implement mutual exclusion while a guard variable cannot?
/* Include the semaphore header file */
#include <semaphore.h>
/* Declare our global semaphore */
static sem_t semaphore;
/* Initialise our semaphore */
(void) sem_init(&semaphore, 0, 1);
/* Destroy our semaphore */
(void) sem_destroy(&semaphore);
/* Wait for lock */
(void) sem_wait(&semaphore);
/* Release lock */
(void) sem_post(&semaphore);