Linux Tutorial: POSIX Threads (original) (raw)

Thread Basics:

Thread Creation and Termination:

Example: pthread1.c

#include <stdio.h> #include <stdlib.h> #include <pthread.h>

void *print_message_function( void *ptr );

main() { pthread_t thread1, thread2; const char *message1 = "Thread 1"; const char *message2 = "Thread 2"; int iret1, iret2;

/* Create independent threads each of which will execute function */

 iret1 = pthread_create( &thread1, NULL, print_message_function, (void*) message1);
 if(iret1)
 {
     fprintf(stderr,"Error - pthread_create() return code: %d\n",iret1);
     exit(EXIT_FAILURE);
 }

 iret2 = pthread_create( &thread2, NULL, print_message_function, (void*) message2);
 if(iret2)
 {
     fprintf(stderr,"Error - pthread_create() return code: %d\n",iret2);
     exit(EXIT_FAILURE);
 }

 printf("pthread_create() for thread 1 returns: %d\n",iret1);
 printf("pthread_create() for thread 2 returns: %d\n",iret2);

 /* Wait till threads are complete before main continues. Unless we  */
 /* wait we run the risk of executing an exit which will terminate   */
 /* the process and all threads before the threads have completed.   */

 pthread_join( thread1, NULL);
 pthread_join( thread2, NULL); 

 exit(EXIT_SUCCESS);

}

void *print_message_function( void *ptr ) { char *message; message = (char *) ptr; printf("%s \n", message); }

Compile:

The GNU compiler now has the command line option "-pthread" while older versions of the compiler specify the pthread library explicitly with "-lpthread".

Run: ./a.out

Results:

Thread 1 Thread 2 Thread 1 returns: 0 Thread 2 returns: 0

Details:

Arguments:

Thread Synchronization:

The threads library provides three synchronization mechanisms:


Mutexes:

Mutexes are used to prevent data inconsistencies due to operations by multiple threads upon the same memory area performed at the same time or to prevent race conditions where an order of operation upon the memory is expected. A contention or race condition often occurs when two or more threads need to perform operations on the same memory area, but the results of computations depends on the order in which these operations are performed. Mutexes are used for serializing shared resources such as memory. Anytime a global resource is accessed by more than one thread the resource should have a Mutex associated with it. One can apply a mutex to protect a segment of memory ("critical region") from other threads. Mutexes can be applied only to threads in a single process and do not work between processes as do semaphores.

Example threaded function:

Without Mutex With Mutex
int counter=0; /* Function C */ void functionC() { counter++ } /* Note scope of variable and mutex are the same */ pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER; int counter=0; /* Function C */ void functionC() { pthread_mutex_lock( &mutex1 ); counter++ pthread_mutex_unlock( &mutex1 ); }
Possible execution sequence
Thread 1 Thread 2 Thread 1 Thread 2
counter = 0 counter = 0 counter = 0 counter = 0
counter = 1 counter = 1 counter = 1 Thread 2 locked out.Thread 1 has exclusive use of variable counter
counter = 2

If register load and store operations for the incrementing of variable counteroccurs with unfortunate timing, it is theoretically possible to have each thread increment and overwrite the same variable with the same value. Another possibility is that thread two would first increment counterlocking out thread one until complete and then thread one would increment it to 2.

Sequence Thread 1 Thread 2
1 counter = 0 counter=0
2 Thread 1 locked out.Thread 2 has exclusive use of variable counter counter = 1
3 counter = 2

Code listing:

mutex1.c

#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); }

/* Wait till threads are complete before main continues. Unless we / / wait we run the risk of executing an exit which will terminate / / the process and all threads before the threads have completed. */

pthread_join( thread1, NULL); pthread_join( thread2, NULL);

exit(EXIT_SUCCESS); }

void *functionC() { pthread_mutex_lock( &mutex1 ); counter++; printf("Counter value: %d\n",counter); pthread_mutex_unlock( &mutex1 ); }

Compile: cc -pthread mutex1.c (or cc -lpthread mutex1.c for older versions of the GNU compiler which explicitly reference the library)
Run: ./a.out
Results:

Counter value: 1
Counter value: 2

When a mutex lock is attempted against a mutex which is held by another thread, the thread is blocked until the mutex is unlocked. When a thread terminates, the mutex does not unless explicitly unlocked. Nothing happens by default.

Man Pages:


Joins:

A join is performed when one wants to wait for a thread to finish. A thread calling routine may launch multiple threads then wait for them to finish to get the results. One waits for the completion of the threads with a join.

Sample code: join1.c

#include <stdio.h> #include <pthread.h>

#define NTHREADS 10 void *thread_function(void *); pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER; int counter = 0;

main() { pthread_t thread_id[NTHREADS]; int i, j;

for(i=0; i < NTHREADS; i++) { pthread_create( &thread_id[i], NULL, thread_function, NULL ); }

for(j=0; j < NTHREADS; j++) { pthread_join( thread_id[j], NULL); }

/* Now that all threads are complete I can print the final result. / / Without the join I could be printing a value before all the threads / / have been completed. */

printf("Final counter value: %d\n", counter); }

void *thread_function(void *dummyPtr) { printf("Thread number %ld\n", pthread_self()); pthread_mutex_lock( &mutex1 ); counter++; pthread_mutex_unlock( &mutex1 ); }

Compile: cc -pthread join1.c (or cc -lpthread join1.c for older versions of the GNU compiler which explicitly reference the library)
Run: ./a.out
Results:

Thread number 1026 Thread number 2051 Thread number 3076 Thread number 4101 Thread number 5126 Thread number 6151 Thread number 7176 Thread number 8201 Thread number 9226 Thread number 10251 Final counter value: 10

Man Pages:


Condition Variables:

A condition variable is a variable of type pthread_cond_t and is used with the appropriate functions for waiting and later, process continuation. The condition variable mechanism allows threads to suspend execution and relinquish the processor until some condition is true. A condition variable must always be associated with a mutex to avoid a race condition created by one thread preparing to wait and another thread which may signal the condition before the first thread actually waits on it resulting in a deadlock. The thread will be perpetually waiting for a signal that is never sent. Any mutex can be used, there is no explicit link between the mutex and the condition variable.

Man pages of functions used in conjunction with the condition variable:

Example code:

cond1.c

#include <stdio.h> #include <stdlib.h> #include <pthread.h>

pthread_mutex_t count_mutex = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t condition_var = PTHREAD_COND_INITIALIZER;

void *functionCount1(); void *functionCount2(); int count = 0; #define COUNT_DONE 10 #define COUNT_HALT1 3 #define COUNT_HALT2 6

main() { pthread_t thread1, thread2;

pthread_create( &thread1, NULL, &functionCount1, NULL); pthread_create( &thread2, NULL, &functionCount2, NULL);

pthread_join( thread1, NULL); pthread_join( thread2, NULL);

printf("Final count: %d\n",count);

exit(EXIT_SUCCESS); }

// Write numbers 1-3 and 8-10 as permitted by functionCount2()

void *functionCount1() { for(;;) { // Lock mutex and then wait for signal to relase mutex pthread_mutex_lock( &count_mutex );

  // Wait while functionCount2() operates on count
  // mutex unlocked if condition varialbe in functionCount2() signaled.
  pthread_cond_wait( &condition_var, &count_mutex );
  count++;
  printf("Counter value functionCount1: %d\n",count);

  pthread_mutex_unlock( &count_mutex );

  if(count >= COUNT_DONE) return(NULL);
}

}

// Write numbers 4-7

void *functionCount2() { for(;;) { pthread_mutex_lock( &count_mutex );

   if( count < COUNT_HALT1 || count > COUNT_HALT2 )
   {
      // Condition of if statement has been met. 
      // Signal to free waiting thread by freeing the mutex.
      // Note: functionCount1() is now permitted to modify "count".
      pthread_cond_signal( &condition_var );
   }
   else
   {
      count++;
      printf("Counter value functionCount2: %d\n",count);
   }

   pthread_mutex_unlock( &count_mutex );

   if(count >= COUNT_DONE) return(NULL);
}

}

Compile: cc -pthread cond1.c (or cc -lpthread cond1.c for older versions of the GNU compiler which explicitly reference the library)
Run: ./a.out
Results:

Counter value functionCount1: 1 Counter value functionCount1: 2 Counter value functionCount1: 3 Counter value functionCount2: 4 Counter value functionCount2: 5 Counter value functionCount2: 6 Counter value functionCount2: 7 Counter value functionCount1: 8 Counter value functionCount1: 9 Counter value functionCount1: 10 Final count: 10

Note that functionCount1() was halted while count was between the values COUNT_HALT1 and COUNT_HALT2. The only thing that has been ensures is that functionCount2 will increment the count between the values COUNT_HALT1 and COUNT_HALT2. Everything else is random.

The logic conditions (the "if" and "while" statements) must be chosen to insure that the "signal" is executed if the "wait" is ever processed. Poor software logic can also lead to a deadlock condition.

Note: Race conditions abound with this example because count is used as the condition and can't be locked in the while statement without causing deadlock.

Thread Scheduling:

When this option is enabled, each thread may have its own scheduling properties. Scheduling attributes may be specified:

The threads library provides default values that are sufficient for most cases.

Thread Pitfalls:

The order of applying the mutex is also important. The following code segment illustrates a potential for deadlock:
void *function1()
{
...
pthread_mutex_lock(&lock1); // Execution step 1
pthread_mutex_lock(&lock2); // Execution step 3 DEADLOCK!!!
...
...
pthread_mutex_lock(&lock2);
pthread_mutex_lock(&lock1);
...
}
void *function2()
{
...
pthread_mutex_lock(&lock2); // Execution step 2
pthread_mutex_lock(&lock1);
...
...
pthread_mutex_lock(&lock1);
pthread_mutex_lock(&lock2);
...
}

main()  
{  
   ...  
   pthread_create(&thread1, NULL, function1, NULL);  
   pthread_create(&thread2, NULL, function2, NULL);  
   ...  
}  
  

If function1 acquires the first mutex and function2 acquires the second, all resources are tied up and locked.

Thread Debugging:

Thread Man Pages:

Links:

News Groups:

Books:

Pthreads Programming A POSIX Standard for Better Multiprocessing By Bradford Nichols, Dick Buttlar, Jacqueline Proulx Farrell ISBN #1-56592-115-1, O'Reilly Amazon.com
Programming with POSIX(R) Threads By David R. Butenhof ISBN #0201633922, Addison Wesley Pub. Co. Amazon.com
C++ Network Programming Volume 1 By Douglas C. Schmidt, Stephen D. Huston ISBN #0201604647, Addison Wesley Pub. Co. Covers ACE (ADAPTIVE Communication Environment) open-source framework view of threads and other topics. Amazon.com