POSIX Mutexes
FreeBSD supports Mutexes specified in POSIX for thread synchronization.
1. Include file
#include <pthread.h
- Mutex Creation - There are two ways to create and initialize a mutex.
- Define a variable of type pthread_mutex_t and initialize it with the special value PTHREAD_MUTEX_INITIALIZER
pthread_mutex_t myMutex = PTHREAD_MUTEXT_INITIALIZER;
- Define a variable of type pthread_mutex_t and then call pthread_mutex_init() to initialize it.
pthread_mutex_t myMutex;
pthread_mutex_init(myMutex, NULL);
the second argument of pthread_mutext_init() is a pointer to a mutex attribute object and when it is NULL, default attributes are assumed.
- Lock and Unlock operations
pthread_mutex_lock(myMutex);
pthread_mutex_unlock(myMutex);
Note: pthread_mutext_unlock unblocks only one of the blocked/waiting threads on the mutex and the one is chosen unpredictably. The pthread_mutext_unlock() should always be called by the same thread that locked the mutex.
4. Release Mutexes
When a mutex is no longer needed, it should be released or destroyed. The pthread_mutex_destroy() destroys the mutex object and the object becomes uninitialized.
pthread_mutex_destroy(myMutex);
A mutex object may be re-initialized and then reused.
5. An Example
#include <stdio.h
#include <stdlib.h
#include <pthread.h
void *functionC();
pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;
int counter = 0;
main()
{
int rc1, rc2;
pthread_t thread1, thread2;
/* Create independent threads each of which will execute functionC */
if((rc1=pthread_create( &thread1, NULL, &functionC, NULL))) {
printf("Thread creation failed: %d\n", rc1);
}
if((rc2=pthread_create( &thread2, NULL, &functionC, NULL))) {
printf("Thread creation failed: %d\n", rc2);
}
pthread_join( thread1, NULL);
pthread_join( thread2, NULL);
pthread_mutex_destroy(&mutex1);
exit(0);
}
void *functionC()
{
pthread_mutex_lock( &mutex1 );
counter++;
printf("Counter value: %d\n",counter);
pthread_mutex_unlock( &mutex1 );
}
POSIX Semaphores for Threads
FreeBSD implements Semaphores specified in POSIX for thread synchronization and process synchronization.
1. Include File
#include <semaphore.h
- Creation
sem_t mySema;
sem_init(mySema, 0, initValue);
The second parameter should be zero when the semaphore is used by threads, the last parameter is the initial value of the semaphore. If the second parameter is a non-zero value, the semaphore is shared between processes.
- Down/up Operations
Down operation: sem_wait(mySema);
Up operation: sem_post(mySema);
4. Release Semaphores
When a semaphore is no longer needed, it should be released or destroyed. The sem_destroy() destroys the sem_t object and the object becomes uninitialized.
sem_destroy(mySema);
A semaphore object may be re-initialized and then reused.
The effect of this operation is undefined if there are threads who are blocked on the semaphore.
5. An Example
.
#include <stdio.h
#include <stdlib.h
#include <pthread.h
#include <semaphore.h
#include <sys/types.h
sem_t mutex;
int shared_int = 100; // shared by the two threads
// function executed by thread 1
void *increment_shared_int( void *ptr )
{
int *increment = (int*)ptr;
int i;
sem_wait(mutex);
for (i = 0; i < 10; i++) {
shared_int += *increment;
printf("Thread 1 - %d\n", shared_int);
sleep(2);
}
sem_post(mutex);
}
// function executed by thread 2
void *decrement_shared_int( void *ptr )
{
int *decrement = (int*)ptr;
int i;
sem_wait(mutex);
for (i = 0; i < 10; i++) {
shared_int -= *decrement;
printf("Thread 2 - %d\n", shared_int);
sleep(2);
}
sem_post(mutex);
}
main()
{
pthread_t thread1, thread2;
int increment = 10;
int decrement = 5;
int iret1, iret2;
sem_init(mutex, 0, 1);
// create and start the two threads
iret1 = pthread_create( &thread1, NULL, increment_shared_int, (void*) &increment);
iret2 = pthread_create( &thread2, NULL, decrement_shared_int, (void*) &decrement);
printf("Main wating for child thread #1\n");
pthread_join( thread1, NULL);
printf("Main wating for child thread #2\n");
pthread_join( thread2, NULL);
printf("both threads have terminated\n");
sem_destroy(mutex);
exit(0);
}
POSIX Conditions
POSIX specifies condition variables. A thread can wait or be blocked on a condition variable, and another thread may unblock a thread waiting on the same condition variable. Often condition variables are used together with mutexes to implement, for example, monitors.
1. Include file
#include <pthread.h
- Condition Creation - There are two ways to create and initialize a condition variable.
- Using pthread_cond_init()
pthread_cond_t availSlot;
pthread_cond_init(availSlot, NULL);
the second argument of pthread_cond_init() is a pointer to a condition attribute object and when it is NULL, default attributes are assumed.
- Using special value PTHREAD_COND_INITIALIZER
If a condition variable only takes the default attributes, it can be defined and initialized by special value, PTHREAD_COND_INITIALIZER
Pthread_cond_t availSlot = PTHREAD_COND_INITIALIZER;
- Pthread_cond_wait
pthread_cond_wait(availSlot, &myMutex);
The first argument is a pointer to a condition variable and the second argument is a pointer to a mutex. The effect of this function is to unlock the mutex and blocks the calling thread on the condition variable, which are all done as one atomic operation. When this function is invoked, the mutex must already be locked by the calling thread.
- Pthread_cond_signal and pthread_cond_broadcast
pthread_cond_signal(availSlot);
pthread_cond_broadcast(availSlot);
The argument of the two functions is a pointer to a condition variable. If there are threads waiting on the condition variable, pthread_cond_signal unblocks at least one of the blocked threads, and pthread_cond_broadcast unblocks all of them. The threads that are unblocked contend for the mutex associated to the condition variable as if each had called pthread_mutext_lock(). If the caller of pthread_cond_signal or pthread_cond_broadcast owns the mutex associated with the condition variable, it still owns the mutex until it unlocks it using pthread_mutext_unlock. If there is no thread blocked on the condition variable, the functions would have no effect on the condition variable.
5. Release Conditions
When a condition is no longer needed, it should be released or destroyed. The pthread_cond_destory() destroys the condition object and the object becomes uninitialized.
pthread_cond_destroy(availSlot);
A condition object may be re-initialized and then reused.
The effect of this operation is undefined if it is applied to a condition variable on which there are threads blocked.
6. An Example
The following example uses a mutex and two conditions to implement the bounded buffer or the producers and consumers problem. The Buffer contains a mutex, and two condition variables, buffer_full and buffer_empty. When a producer tries to deposit when the buffer is full, it waits on buffer_full. After a consumer removes an item from the buffer, it signals (or broadcast to unblock all blocked producers) the buffer_full condition. When a consumer tries to remove items from an empty buffer, it waits on buffer_empty. Each producer signals (or broadcasts) buffer_empty after depositing an item in the buffer.
Please note that pthread_cond_wait for both producers and consumers are wrapped in a while loop, rechecking the condition (if the buffer is still full in producer and if the buffer is empty in consumer). This is because the condition may and may not be true when a thread gets the CPU after waking up from pthread_cond_wait since other threads may have been executed first and thus changed the condition.
#include <stdio.h
#include <stdlib.h
#include <pthread.h
#include <sys/types.h
#define SIZE 5
typedef struct BUFFER {
pthread_mutex_t mutex;
pthread_cond_t buffer_full;
pthread_cond_t buffer_empty;
int list[SIZE];
int count;
int first;
} Buffer;
void *producer_function(void *ptr)
{
int i;
Buffer *buffer = (Buffer *)ptr;
for (i = 0; i < 20; i++) {
pthread_mutex_lock(&buffer->mutex);
while (buffer->count >= SIZE) {
printf("producer waits\n");
pthread_cond_wait(&buffer->buffer_full, &buffer->mutex);
printf("producer wakes up\n");
}
printf("Producer deposits - %d\n", i+200);
buffer->list[(buffer->count + buffer->first ) % SIZE] = i + 200;
buffer->count = buffer->count + 1;
pthread_cond_signal(&buffer->buffer_empty);
pthread_mutex_unlock(&buffer->mutex);
sleep(2);
}
printf("Produce exit\n");
}
void *consumer_function(void *ptr)
{
int i, item = -1;
Buffer *buffer = (Buffer *)ptr;
for (i = 0; i < 20; i++) {
pthread_mutex_lock(&buffer->mutex);
while (buffer->count == 0) {
printf("consumer waits\n");
pthread_cond_wait(&buffer->buffer_empty, &buffer->mutex);
printf("consumer wakes up\n");
}
item = buffer->list[buffer->first];
buffer->first = (buffer->first + 1) % SIZE;
printf("consumer removes - %d\n", item);
buffer->count = buffer->count - 1;
pthread_cond_signal(&buffer->buffer_full);
pthread_mutex_unlock(&buffer->mutex);
sleep(4);
}
printf("consumer exit\n");
}
int main() {
pthread_t producer, consumer;
Buffer buffer;
int iret1, iret2;
buffer.first = 0;
buffer.count = 0;
pthread_mutex_init(buffer.mutex, NULL);
pthread_cond_init(buffer.buffer_full, NULL);
pthread_cond_init(buffer.buffer_empty, NULL);
// create and start the two threads
iret1 = pthread_create(&producer, NULL, producer_function, (void*) &buffer);
iret2 = pthread_create(&consumer, NULL, consumer_function, (void*) &buffer);
printf("Main wating for producer \n");
pthread_join(producer, NULL);
printf("Main wating for consumer\n");
pthread_join(consumer, NULL);
printf("both producer and consumer have terminated\n");
pthread_mutex_destroy(buffer.mutex);
exit(0);
}