Search code examples
cmultithreadingcondition-variable

C - pthread condition variables


So I have this while-loop that does some work with multiple threads and I want it to work as long as all threads are working, something like:

while(*threads are working*) {
 pthread_mutex_lock 
 if(stack is not empty) {
     pthread_cond_broadcast
     *critical work*
     pthread_mutex_unlock
 }
 else {
     pthread_cond_wait
     pthread_mutex_unlock
 }

I basically want this while-loop to run until ALL threads have checked if the stack is empty and are waiting in the else case. All tips are very welcome, thanks.


Solution

  • Remember that condition variables are simply signalling that some condition in the enclosing program has changed. The most important thing when using condition variables is to realize what that condition is and ensuring that it's modeled correctly. The condition is often also called the predicate.

    In your case your threads act as both producers and consumers of work on the shared stack. If a thread runs out of work, it will enter a wait state from which it should only return if one of the following conditions is met:

    • Some other thread pushes work on the stack. In that case you want your thread to wake up to help with the newly pushed work.
    • All threads have entered the wait state. In that case, there is no more work left and since all threads are done, no work will be pushed to the stack anymore.

    The disjunction of those two conditions form your predicate.

    The first condition is already modeled in the program, as you can simply inspect the stack to find out if any new work is available. The second condition however is not. You have no way of checking how many threads are currently in the wait state.

    The solution is to model that condition also, which is easily done by introducing a counter:

    int threads_waiting = 0;
    while(true) {
      pthread_mutex_lock 
      if(stack is not empty) {
        *critical work*
        if(i_pushed_some_work_on_the_stack) {
          pthread_cond_broadcast   // wake up any threads that have gone to sleep
                                   // because the stack ran out of work
        }
        pthread_mutex_unlock
      } else {
        ++threads_sleeping
        if(threads_sleeping == number_of_threads) {
          pthread_cond_broadcast     // wake up any threads waiting for
                                     // the last thread to finish                  
          pthread_mutex_unlock       // ... and we're done!
          return
        }
        while(true) {
          pthread_cond_wait
          if(stack is not empty) {
            // there is more work available; continue outer loop
            --threads_sleeping
            break;
          } else if(threads_sleeping == number_of_threads) {
            // everybody is done, so let's return
            pthread_mutex_unlock
            return
          } else {
            // spurious wakeup; go back to sleep
          }
        }
        pthread_mutex_unlock
    }
    

    Note how we call pthread_cond_broadcast whenever the predicate changes and that after returning from pthread_cond_wait we inspect the enclosing conditions to figure out what to do next.