badcnt.c
creates
two new threads, both of which increment a global
variable called cnt
exactly NITER
, with
NITER = 1,000,000
.
But the program produces unexpected results.
badcnt.c
and compile it using
gcc badcnt.c -o badcnt -lpthreadRun the executable badcnt and observe the ouput. Try it on both tanner and felix.
Quite unexpected! Since cnt
starts at 0, and both
threads
increment it NITER
times, we should see cnt equal
to 2*NITER
at the end of the program. What happens?
Threads can greatly simplify writing elegant and efficient programs. However,
there are problems when multiple threads share a common address
space, like the variable cnt
in our earlier example.
To understand what might happen, let us analyze this simple piece of code:
THREAD 1 THREAD 2 a = data; b = data; a++; b--; data = a; data = b;
Now if this code is executed serially (for instance, THREAD 1 first and then THREAD 2), there are no problems. However threads execute in an arbitrary order, so consider the following situation:
Thread 1 | Thread 2 | |
a = data; | ||
a = a+1; | ||
b = data; // 0 | ||
b = b + 1; | ||
data = a; // 1 | ||
data = b; // 1 |
So data could end up +1, 0, -1, and there is NO WAY to know which value! It is completely non-deterministic!
The solution to this is to provide functions that will block a thread if another thread is accessing data that it is using.
Pthreads may use semaphores to achieve this.
All POSIX semaphore functions and types are prototyped or defined in semaphore.h. To define a semaphore object, use
sem_t sem_name;
To initialize a semaphore, use sem_init():
int sem_init(sem_t *sem, int pshared, unsigned int value);
sem_init(&sem_name, 0, 10);
int sem_wait(sem_t *sem);Example of use:
sem_wait(&sem_name);
int sem_post(sem_t *sem);Example of use:
sem_post(&sem_name);
int sem_getvalue(sem_t *sem, int *valp);
int value; sem_getvalue(&sem_name, &value); printf("The value of the semaphors is %d\n", value);
int sem_destroy(sem_t *sem);
sem_destroy(&sem_name);
Declare the semaphore global (outside of any funcion): sem_t mutex; Initialize the semaphore in the main function: sem_init(&mutex, 0, 1);
Thread 1 | Thread 2 | |
sem_wait (&mutex); | ||
sem_wait (&mutex); | ||
a = data; | ||
a = a+1; | ||
data = a; | ||
sem_post (&mutex); | ||
b = data; | ||
b = b + 1; | ||
data = b; | ||
sem_post (&mutex); | ||
Exercise 2.Use the example above
as a guide to fix the program
badcnt.c
, so that
the program always produces the expected output (the value
2*NITER). Make a copy of badcnt.c
into
goodcnt.c
before you modify the code.
To compile a program that uses pthreads and posix semaphores, use
gcc -o filename filename.c -lpthread -lrt
Exercise 3. Download this incomplete producer-consumer code in your posixsem directory. Complete the downloaded code to implement a solution to the Producer-Consumer problem using Posix threads and semaphores.
Comment well your code. Compile and run your program and observe the output. Label each line in the output by the identifier for each producer and consumer (P1, P2, P3, C1, C2, C3). The output of your program should be similar to the following:
[P0] Producing 0 ... [P1] Producing 0 ... [P2] Producing 0 ... [P2] Producing 1 ... ------> [C2] consumed 0 ------> [C2] consumed 1 [P1] Producing 1 ... ------> [C1] consumed 0 ------> [C1] consumed 1 [P0] Producing 1 ... ------> [C1] consumed 1 ------> [C0] consumed 0 [P2] Producing 2 ... [P2] Producing 3 ... [P1] Producing 2 ... [P1] Producing 3 ... ------> [C0] consumed 2 ------> [C0] consumed 3 ------> [C0] consumed 2 ------> [C1] consumed 3 [P0] Producing 2 ... [P0] Producing 3 ... ------> [C2] consumed 2 ------> [C2] consumed 3
To compile a program that uses pthreads and posix semaphores, use
gcc -o filename filename.c -lpthread -lrt