4. Coordinating Activities With Semaphores

Let us revisit our readers/writers program using semaphores. We will replace the mutex primitive with the more robust integer semaphore and eliminate the spin-lock problem. Semaphore operations are semaphore_up, semaphore_down, semaphore_init, semaphore_destroy, and semaphore_decrement. The up and down functions conform to traditional semaphore semantics -- the down operation blocks if the semaphore has a value less than or equal to zero and the up operation increments the semaphore. The init function must be called prior to semaphore use and all semaphores are initialized with a value of one. The destroy function releases the semaphore if it is no longer used. All functions take a single argument that is a pointer to a semaphore object.

Semaphore decrement is a non-blocking function that decrements the value of the semaphore. It allows threads to decrement the semaphore to some negative value as part of an initialization process. We will look at an example that uses semaphore_decrement after the readers/writers program.

  void reader_function(void);
  void writer_function(void);
  
  char buffer;
  Semaphore writers_turn;
  Semaphore readers_turn;
  
  main()
  {
     pthread_t reader;
  
     semaphore_init( &readers_turn );
     semaphore_init( &writers_turn );
  
     /* writer must go first */
     semaphore_down( &readers_turn );
  
     pthread_create( &reader, pthread_attr_default, 
                    (void *)&reader_function, NULL);
     writer_function();
  }
  
  void writer_function(void)
  {
     while(1)
     {
          semaphore_down( &writers_turn );
          buffer = make_new_item();
          semaphore_up( &readers_turn );
     }
  }
  
  void reader_function(void)
  {
     while(1)
     {
          semaphore_down( &readers_turn );
          consume_item( buffer );
          semaphore_up( &writers_turn );
     }
  }
This example still does not fully utilize the power of the general integer semaphore. Let us revise the hello world program from Section 2 and fix the race conditions using the integer semaphore.
void print_message_function( void *ptr );

Semaphore child_counter;
Semaphore worlds_turn;

main()
{
     pthread_t thread1, thread2;
     char *message1 = "Hello";
     char *message2 = "World";

     semaphore_init( &child_counter );
     semaphore_init( &worlds_turn );

     semaphore_down( &worlds_turn ); /* world goes second */
     
     semaphore_decrement( &child_counter ); /* value now 0 */
     semaphore_decrement( &child_counter ); /* value now -1 */
     /* 
      * child_counter now must be up-ed 2 times for a thread blocked on it
      * to be released 
      *
      */
     

     pthread_create( &thread1, pthread_attr_default, 
                    (void *) &print_message_function, (void *) message1);

     semaphore_down( &worlds_turn );

     pthread_create(&thread2, pthread_attr_default, 
                    (void *) &print_message_function, (void *) message2);

     semaphore_down( &child_counter );

     /* not really necessary to destroy since we are exiting anyway */
     semaphore_destroy ( &child_counter );
     semaphore_destroy ( &worlds_turn );
     exit(0);
}

void print_message_function( void *ptr )
{
     char *message;
     message = (char *) ptr;
     printf("%s ", message);
     fflush(stdout); 
     semaphore_up( &worlds_turn );
     semaphore_up( &child_counter );
     pthread_exit(0);
}
Readers can easily satisfy themselves that there are no race conditions in this version of our hello world program and that the words are printed in the proper order. The semaphore child_counter is used to force the parent thread to block until both children have executed the printf statement and the following semaphore_up( &child_counter ).

Functions covered in this section:

	semaphore_init(), semaphore_up(), semaphore_down(), 
	semaphore_destroy(), and semaphore_decrement().


Move to: Contents or Previous Section or Next Section