Home > Articles > Programming > General Programming/Other Languages

  • Print
  • + Share This
Like this article? We recommend

Like this article? We recommend

Reading, Writing, and Arithmetic

Condition variables are useful for synchronization, and mutexes are good for protecting critical sections. Often, however, you’ll have a resource that several threads want to be able to read at once. This isn’t a problem; as many threads as need to can read a memory location at once, without issue. The problem comes when one thread wants to write to that memory location.

If one thread modifies a shared resource, any other threads that are reading that resource will be in an inconsistent state. The locking model required for this scenario has the following properties:

  • Anyone can gain read access to a resource, as long as no one has write access.
  • Write access can be granted only when no one has read access.

Again, pthreads come with a built-in mechanism for this situation, in the form of read/write locks. These locks have one extra feature that make it easier to acquire write locks: No one is allowed to gain a read lock when someone is waiting for a write lock. This means that a thread trying to gain write access will block any thread trying to gain read access until it has finished.

The example in Listing 4 again has four threads. The first one fills an array with random numbers. While it’s doing this, it has a write lock. Once the thread has finished, it broadcasts on a condition variable, waking up three other threads. These threads then acquire a read lock and generate the total, average, and maximum value for the array.

Listing 4 calculate.c.

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <math.h>
#include <unistd.h>
#include <limits.h>

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t condition = PTHREAD_COND_INITIALIZER;
pthread_rwlock_t lock;

#define NUMBERS 100
int numbers[NUMBERS];
int averageRun = 0;
int sumRun = 0;
int maxRun = 0;

void * makerandom(void* spare)
{
    while(1)
    {
       pthread_mutex_lock(&mutex);
       pthread_rwlock_wrlock(&lock);
       printf("Generating numbers...\n");
       for(unsigned int i=0 ; i<NUMBERS ; i++)
       {
           numbers[i] = random();
       }
       //Unlock the array when we have finished with it
       pthread_rwlock_unlock(&lock);
       //Set a flag so that the other functions run if they miss the condition variable
       maxRun = averageRun = sumRun = 1;
       //Wake everyone else up...
       pthread_cond_broadcast(&condition);
       //...and let them run
       pthread_mutex_unlock(&mutex);
       sleep(1);
    }
}

void * sum(void * spare)
{
    while(1)
    {
       pthread_mutex_lock(&mutex);
       //Only wait on the condition variable if there is nothing else to do
       if(sumRun == 0)
       {
           pthread_cond_wait(&condition, &mutex);
       }
       //Protect the array while we look at it
       pthread_rwlock_rdlock(&lock);
       if(sumRun == 1)
       {
           //Unset the flag before releasing the mutex so we don’t miss it being set again
           sumRun = 0;
           pthread_mutex_unlock(&mutex);

           long long total = 0;
           for(unsigned int i=0 ; i<NUMBERS ; i++)
           {
              total += numbers[i];
           }
           printf("Total = %lld\n",total);
       }
       else
       {
           //Release the mutex if we didn’t do anything with the array
           pthread_mutex_unlock(&mutex);
       }
       sleep(1);
       //Unlock the array
       pthread_rwlock_unlock(&lock);
    }
}

void * average(void * spare)
{
    while(1)
    {
       pthread_mutex_lock(&mutex);
       if(averageRun == 0)
       {
           pthread_cond_wait(&condition, &mutex);
       }
       pthread_rwlock_rdlock(&lock);
       if(averageRun == 1)
       {
           averageRun = 0;
           pthread_mutex_unlock(&mutex);
           long long total = 0;
           for(unsigned int i=0 ; i<NUMBERS ; i++)
           {
              total += numbers[i];
           }
           printf("Average = %lld\n",total / NUMBERS);
       }
       else
       {
           pthread_mutex_unlock(&mutex);
       }
       pthread_rwlock_unlock(&lock);
    }
}
void * max(void * spare)
{
    while(1)
    {
       pthread_mutex_lock(&mutex);
       if(maxRun == 0)
       {
           pthread_cond_wait(&condition, &mutex);
       }
       pthread_rwlock_rdlock(&lock);
       if(maxRun == 1)
       {
           maxRun = 0;
           pthread_mutex_unlock(&mutex);
           int max = -INT_MAX;
           for(unsigned int i=0 ; i<NUMBERS ; i++)
           {
              if(numbers[i] > max)
              {
                  max = numbers[i];
              }
           }
           printf("Max = %d\n",max);
       }
       else
       {
           pthread_mutex_unlock(&mutex);
       }
       pthread_rwlock_unlock(&lock);
    }
}
int main(void)
{
    pthread_t thread1, thread2;
    //Dynamically initialise the rwlock with default attributes
    pthread_rwlock_init(&lock, NULL);
    pthread_create(&thread1, NULL, sum, (void*)1);
    pthread_create(&thread1, NULL, average, (void*)2);
    pthread_create(&thread1, NULL, max, (void*)3);
    pthread_create(&thread2, NULL, makerandom, NULL);
    pthread_join(thread1, NULL);
    pthread_join(thread2, NULL);
    return 0;
}

This example shows all three forms of synchronization control available to POSIX threads:

  • A mutex is used to ensure that only one thread is modifying the run flag at a time.
  • A condition variable is used to notify flags that new values are in an array.
  • A read/write lock is used to protect the array itself.
  • + Share This
  • 🔖 Save To Your Account