3. Thread Synchronization

POSIX provides two thread synchronization primitives, the mutex and the condition variable. Mutexes are simple lock primitives that can be used to control access to a shared resource. Note that with threads, the entire address space is shared so everything can be considered a shared resource. However, in most cases threads work individually with (conceptually) private local variables, those created within the function called by pthread_create and successive functions, and combine their efforts through global variables. Access to the commonly written items must be controlled.

Lets create a readers/writers application where a single reader and a single writer communicate using a shared buffer and access is controlled using a mutex.

  void reader_function(void);
  void writer_function(void);
  
  char buffer;
  int buffer_has_item = 0;
  pthread_mutex_t mutex;
  struct timespec delay;
  
  main()
  {
     pthread_t reader;
  
     delay.tv_sec = 2;
     delay.tv_nsec = 0;
  
     pthread_mutex_init(&mutex, pthread_mutexattr_default);
     pthread_create( &reader, pthread_attr_default, (void*)&reader_function,
                    NULL);
     writer_function();
  }
  
  void writer_function(void)
  {
     while(1)
     {
          pthread_mutex_lock( &mutex );
          if ( buffer_has_item == 0 )
          {
               buffer = make_new_item();
               buffer_has_item = 1;
          }
          pthread_mutex_unlock( &mutex );
          pthread_delay_np( &delay );
     }
  }
  
  void reader_function(void)
  {
     while(1)
     {
          pthread_mutex_lock( &mutex );
          if ( buffer_has_item == 1)
          {
               consume_item( buffer );
               buffer_has_item = 0;
          }
          pthread_mutex_unlock( &mutex );
          pthread_delay_np( &delay );
     }
  }
In this simple program we assume that the buffer can only hold one item so it is always in one of two states, either it has an item or it doesn't. The writer first locks the mutex, blocking until it is unlocked if it is already locked, then checks to see if the buffer is empty. If the buffer is empty, it creates a new item and sets the flag, buffer_has_item, so that the reader will know the buffer now has an item. It then unlocks the mutex and delays for two seconds to give the reader a chance to consume the item. This delay is different from our previous delays in that it is only meant to improve program efficiency. Without the delay, the writer will release the lock and in the next statement attempt to regain the lock again with the intent of creating another item. Its very likely that the reader has not had a chance to consume the item so quickly so the delay is a good idea.

The reader takes a similar stance. It obtains the lock, checks to see if an item has been created, and if so consumes the item. It releases the lock and then delays for a short while giving the writer the chance to create a new item. In this example the reader and writer run forever, generating and consuming items. However, if a mutex is no longer needed in a program it should be released using pthread_mutex_destroy(&mutex). Observe that in the mutex initialization function, which is required, we used the pthread_ mutexattr_default as the mutex attribute. In OSF/1 the mutex attribute serves no purpose what so ever, so use of the default is strongly encouraged.

The proper use of mutexes guarantees the elimination of race conditions. However, the mutex primitive by itself is very weak as it has only two states, locked or unlocked. The POSIX condition variable supplements mutexes by allowing threads to block and await a signal from another thread. When the signal is received, the blocked thread is awaken and attempts to obtain a lock on the related mutex. Thus signals and mutexes can be combined to eliminate the spin-lock problem exhibited by our readers/writers problem. We have designed a library of simple integer semaphores using the pthread mutex and condition variables and will henceforth discus synchronization in that context. The code for the semaphores can be found in Appendix A and detailed information about condition variables can be found in the man pages.

Functions covered in this section:

	pthread_mutex_init(), pthread_mutex_lock(), 
	pthread_mutex_unlock(), and pthread_mutex_destroy().


Move to: Contents or Previous Section or Next Section