Saturday, October 25, 2014

Unix Prog: Condition Variables

1. Condition Variables Concept
A thread must first lock the mutex to change the condition state. Other threads will not notice the change until they acquire the mutex, because the mutex must be locked to able to evaluate the condition.

2. Condition Variable Definitions:

 ubuntu@ip-172-31-23-227:~$ less /usr/include/pthread.h  
 ......  
 /* Initialize condition variable COND using attributes ATTR, or use  
   the default values if later is NULL. */  
 extern int pthread_cond_init (pthread_cond_t *__restrict __cond,  
                const pthread_condattr_t *__restrict __cond_attr)  
    __THROW __nonnull ((1));  
   
 /* Destroy condition variable COND. */  
 extern int pthread_cond_destroy (pthread_cond_t *__cond)  
    __THROW __nonnull ((1));  
   
 /* Wake up one thread waiting for condition variable COND. */  
 extern int pthread_cond_signal (pthread_cond_t *__cond)  
    __THROWNL __nonnull ((1));  
   
 /* Wake up all threads waiting for condition variables COND. */  
 extern int pthread_cond_broadcast (pthread_cond_t *__cond)  
    __THROWNL __nonnull ((1));  
   
 /* Wait for condition variable COND to be signaled or broadcast.  
   MUTEX is assumed to be locked before.  
   
   This function is a cancellation point and therefore not marked with  
   __THROW. */  
 extern int pthread_cond_wait (pthread_cond_t *__restrict __cond,  
                pthread_mutex_t *__restrict __mutex)  
    __nonnull ((1, 2));  
   
 /* Wait for condition variable COND to be signaled or broadcast until  
   ABSTIME. MUTEX is assumed to be locked before. ABSTIME is an  
   absolute time specification; zero is the beginning of the epoch  
   (00:00:00 GMT, January 1, 1970).  
   
   This function is a cancellation point and therefore not marked with  
   __THROW. */  
 extern int pthread_cond_timedwait (pthread_cond_t *__restrict __cond,  
                   pthread_mutex_t *__restrict __mutex,  
                   const struct timespec *__restrict __abstime)  
    __nonnull ((1, 2, 3));  
 ......  

Before a condition variable is used, it must firstly be initialized. We can assign the constant PTHREAD_COND_INITIALIZER or use pthread_cond_init to initialize it.

Before freeing the memory, we can use the pthread_cond_destroy function to reclaim the storage.

We use pthread_cond_wait to wait for a condition to be true.  The caller passes it locked to the function, which then atomically places the calling thread on the list of threads waiting for the condition and unlocks the mutex. When pthread_cond_wait returns, the mutex is again locked.

pthread_cond_timedwait function works the same as the pthread_cond_wait function with the addition of the timeout. With struct timespec, we specify how long we are willing to wait as an ABSOLUTE time instead of relative time.

pthread_cond_signal function will wake up one thread waiting on a condition, pthread_cond_broadcast function will wake up all threads waiting on a condition.

struct timespec:
 ubuntu@ip-172-31-23-227:~$ less /usr/include/linux/time.h  
 ......  
 struct timespec {  
     __kernel_time_t tv_sec;         /* seconds */  
     long      tv_nsec;        /* nanoseconds */  
 };  
 ......  

We can use following code to get the timespec:

3. Example for getting the timespec:
 #include<stdio.h>  
 #include<stdlib.h>  
 #include<sys/time.h>  
 #include<unistd.h>  
   
 void maketimeout(struct timespec *tsp, long minutes)  
 {  
  struct timeval now;  
   
  gettimeofday(&now, NULL);  
   
  tsp->tv_sec = now.tv_sec;  
  tsp->tv_nsec = now.tv_usec*1000; // tv_usec is microseconds, tv_nsec is nano seconds  
  tsp->tv_sec += minutes * 60;  
   
 }  
   
 int main(int argc, char* argv[])  
 {  
  struct timespec tsp;  
  maketimeout(&tsp, 10);  
   
  printf("tsp->tv_sec: %ld\n", tsp.tv_sec);  
  printf("tsp->tv_nsec: %ld\n", tsp.tv_nsec);  
   
  exit(0);  
 }  

4. Example of condition variables:
cond.c:
 #include<stdio.h>  
 #include<stdlib.h>  
 #include<unistd.h>  
 #include<pthread.h>  
   
 struct msg {  
  struct msg *m_next;  
  char *text;  
 };  
   
 struct msg *workq;  
   
 pthread_cond_t qready = PTHREAD_COND_INITIALIZER;  
 pthread_mutex_t qlock = PTHREAD_MUTEX_INITIALIZER;  
   
 void* process_msg(void* vd)  
 {  
  struct msg *mp;  
   
  // The thread launching this function will keep processing message  
  for(;;) {  
   pthread_mutex_lock(&qlock);  
   
   // wait for the enqueue_messsage thread to broadcast signal  
   // Once pthread_cond_wait blocks, qlock mutex is released, then  
   // enqueue_message thread could re-lock the mutex and insert the message  
   while(workq == NULL)  
    pthread_cond_wait(&qready, &qlock);  
   mp = workq;  
   workq = mp->m_next;  
   pthread_mutex_unlock(&qlock);  
   /* ......process message...... */  
   printf("message: %s\n", mp->text);  
  }  
 }  
   
 void enqueue_msg(struct msg *mp)  
 {  
  // Lock the mutex and enqueue message to the head of queue  
  pthread_mutex_lock(&qlock);  
  mp->m_next = workq;  
  workq = mp;  
  pthread_mutex_unlock(&qlock);  
   
  // Unlock the mutex then broadcast signal to allow one thread  
  // wake up who is waiting for the signal on cond qready  
  pthread_cond_signal(&qready);  
 }  
   
 int main(int argc, char* argv[])  
 {  
  // Launch the thread including process_msg, which will block  
  // on pthread_cond_wait, then wait for the signal from main thread  
  pthread_t tid;  
  if(pthread_create(&tid, NULL, process_msg, NULL) != 0) {  
   printf("pthread_create failed.\n");  
   exit(1);  
  }  
   
  // Main thread build one struct message, then call enqueue_msg to  
  // insert message and broadcast the signal  
  sleep(1);  
  struct msg smsg;  
  smsg.text = "Hello world!";  
  enqueue_msg(&smsg);  
  sleep(1);  
   
  exit(0);  
 }  

shell:
After receiving the signal, the process_message thread get out of pthread_cond_wait and processed the message successfully.
 ubuntu@ip-172-31-23-227:~$ ./cond.out  
 message: Hello world!  

No comments:

Post a Comment