#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
pthread_mutex_lock locks an unlocked mutex.
If the mutex is locked by another thread, this routine causes the thread to wait for the mutex to become available.
If the mutex is already locked by the calling thread, the behavior of pthread_mutex_lock depends on the kind of the mutex.
I.e. it's attributes, whether it is: fast, recursive or error checking.
Mutexes - Mutual Exclusion Devices
If pthread_mutex_lock is used to lock a mutex that is already locked by the calling thread, then:
If the mutex is of the fast kind, the calling thread is suspended until the mutex is unlocked, thus effectively causing the calling thread to deadlock.
If the mutex is of the error checking kind, the call returns immediately with the error code EDEADLK.
If the mutex is of the recursive kind, the call succeeds and returns immediately, recording the number of times the calling thread has locked the mutex.
An equal number of pthread_mutex_unlock operations must be performed before the mutex returns to the unlocked state.
Mutexes - Mutual Exclusion Devices
#include <pthread.h>
int pthread_mutex_unlock(phread_mutex_t *mutex);
The mutex should have been locked by the thread unlocking it.
If this is not the case, behaviour depends on what sort of mutex it is. Linux is non-standard here!
Read man pthread_mutex_unlock for gory details, here.
In the case of error checking this will result in an error.
If other threads are waiting to lock this mutex, then one of them will subsequently succeed.
Which one is entirely non-deterministic.
Mutexes - Mutual Exclusion Devices
#include <pthread.h>
int pthread_mutex_trylock(pthread_mutex_t *mutex);
pthread_mutex_trylock() locks a mutex.
If the mutex is already locked, the calling thread does not wait for the mutex to become available.
Instead, pthread_mutex_trylock returns immediately with the error code EBUSY.
Is useful if the thread has something better to do than wait for the mutex.
Mutex Motivation Examples
Consider the following program:
#include <pthread.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#define THREADS 5
static int sum = 1;
void *updater(void *ptr){
sum = sum + 1;
pthread_exit(NULL);
}
int main(void){
int i;
pthread_t threads[THREADS];
for(i = 0; i < THREADS ; i++)
pthread_create(&threads[i],NULL,updater,NULL);
for(i = 0; i < THREADS ; i++)
pthread_join(threads[i],NULL);
fprintf(stderr, "sum = %d\n", sum);
exit(EXIT_SUCCESS);
}
Mutex Motivation Examples
Presumably it is designed so that after execution sum should be incremented THREADS times.
Thus it should print out: sum = 1 + THREADS
In reality this does seem to work almost all the time.
However it is at the mercy of the scheduler.
In a unfortunate world the answer could be any number greater than 1 and no bigger than 1 + THREADS.
Pretty dangerous.
Mutex Motivation Examples
Review bad_example02.c for a slightly more sophisicated, but equally cringe-worthy attempt.
This does seem to work almost all the time, however this is very in-efficient.
void spin(){
int j;
for(j=0; j< delay; j++);
}
void *updater(void *ptr){
int i;
spin(); i = sum;
spin(); i++;
spin(); sum = i;
spin(); pthread_exit(NULL);
}
Mutex Motivation Examples
The Output on turing:
Examples> example02a 100 100
sum = 101
Examples> example02a 100 10000
sum = 55
Examples> example02a 100 1000000
sum = 34
Examples> example02a 100 10000000
sum = 20
Examples> example02a 100 100000000
sum = 3
Examples>
In the simplest form of producer consumer problem:
We have (at least two threads).
A producer that put things into the slots.
A consumer that takes things out of the slots.
The threads use the access functions defined above.
Since access to the buffer is protected by a mutex, the producer and consumer are prevented from stepping on each others toes by either:
removing something while it is still be put in.
putting something in while the previous entry is being extracted.
Synchronization: Producers and Consumers
Consider the simple minded implementation of the producer/consumer in simple.c
void *producer(void * arg1){
int i;
for (i = 1; i <= SUMSIZE; i++)
put_item(i*i);
pthread_exit(NULL);
}
void *consumer(void *arg2){
int i, myitem;
for (i = 1; i <= SUMSIZE; i++) {
get_item(&myitem);
sum += myitem; }
pthread_exit(NULL);
}
Synchronization: Producers and Consumers
The problem with this example is that there is absolutely no synchronization between the consumer and the producer.
As a consequence things could go horribly wrong.
For example:
the consumer could get ahead of the producer and remove items before they have been put in place.
the producer could get carried away and place items in slots, overwriting entries that have yet to be consumed.
In fact on turing if the consumer is created first the sum is 0, while if the producer is created first the answer errs on the other side.
To handle this type of synchronization we use POSIX condition variables.
Summary
Thread Synchronization
Mutexs - Mutual Exclusion Devices
Mutex Motivation Examples
Synchronization: Producers and Consumers
Reading
Chapters 11 and 15 (Section 15.8) from Advanced Programming in the UNIX environment