September 1, 2020

pthread_cond_wait usage

Did you already used directly the pthread library? If it is the case, you had already used the "pthread_cond_wait" function! However, did you correctly used it?

What is pthread_cond_wait?

"pthread_cond_wait" or "wait on a condition" is a function from the pthread library.

This function is ideal to synchronize two threads. Specially useful for initialization sequence, to notify when a message is received, etc...
"pthread_cond_wait" needs to be coupled with a mutex to work correctly. This mutex will be released on entering into our function:

These functions atomically release mutex and cause the calling thread to block on the condition variable cond; atomically here means "atomically with respect to access by another thread to the mutex and then the condition variable". That is, if another thread is able to acquire the mutex after the about-to-block thread has released it, then a subsequent call to pthread_cond_broadcast() or pthread_cond_signal() in that thread shall behave as if it were issued after the about-to-block thread has blocked.

Don't underestimate the usage of this mutex. It is useful for the unexpected wakeups of pthread_cond_wait :

pthread_cond_wait - Spurious wakeups.

At first sight, it looks easy to use "pthread_cond_wait"! We combine this with a single mutex to synchronize two thread and let's roll.
However, did you know that the usage of a Boolean is recommended?

When using condition variables there is always a Boolean predicate involving shared variables associated with each condition wait that is true if the thread should proceed. Spurious wakeups from the pthread_cond_timedwait() or pthread_cond_wait() functions may occur. Since the return frompthread_cond_timedwait() or pthread_cond_wait() does not imply anything about the value of this predicate, the predicate should be re-evaluated upon such return.

As you see, the function is not safety at 100%.
Indeed, Some spurious wakeups can occurs during the process. This case can triggers terrible consequences: Corruptions, unexpected messages, crashes...

A clean solution to use pthread_cond_wait.

For this post, I will feature to you a simple and clean solution to protect pthread_cond_wait at 100%! And this thanks to the combination of a Mutex, Boolean and Condition variable.

How to use correctly pthread_cond_wait:

pthread_cond_t g_condition_wait = PTHREAD_COND_INITIALIZER;
pthread_mutex_t g_mutex_used = PTHREAD_MUTEX_INITIALIZER;
int g_is_notified = 0;


void* function_thread()
{
    pthread_mutex_lock(&g_mutex_used);

    long_initialization_3_phase("THREAD");

    g_is_notified = 1;
    pthread_cond_signal(&g_condition_wait);
    printf("[THREAD] Initalization completed, notifying main!\n");
    pthread_mutex_unlock(&g_mutex_used);

    return NULL;
}

int main()
{
    pthread_t my_thread;

    pthread_create(&my_thread, NULL, function_thread, NULL);

    pthread_mutex_lock(&g_mutex_used);
		
    while (g_is_notified == 0)
    {
        // Wait for signal. g_is_notified is protected by the Mutex.
        pthread_cond_wait(&g_condition_wait, &g_mutex_used);
    }

    printf("[MAIN] Notified! Starting initialization\n");

    long_initialization_3_phase("MAIN");

    pthread_mutex_unlock(& g_mutex_used);
    pthread_join(my_thread, NULL);


    return 0;
}

A quick explanation... Let's focus on the main function.

Here, "pthread_cond_wait" is surrounded by a while loop conditioned on the boolean "g_is_notified". Thanks to the "g_mutex_used"  lock, this Boolean is fully protected.
As we saw below, the mutex is unlocked during the "pthread_cond_wait" call. It allows an assignment of "g_is_notified" by the second thread represented by the function "function_thread". Then, three cases:

  • Nominal scenario : The boolean "g_is_notified" is set. The signal call will unblock our "pthread_cond_wait". The loop is correctly exited.
  • Spurious WakeUp: Great! In this case, we return directly to "pthread_cond_wait"!
  • Boolean modified before entering in the loop: Can happens following to your configuration... In that case, the condition status is "memorized" by the Boolean. We never enter in "pthread_cond_wait".

As you see, it covers all cases and risks. Here, the thread is correctly protected and initialized!
In all cases, our Boolean is fully protected by the mutex "g_mutex_used" taken in the main and the second thread.

Now, let's run the code!

[THREAD] Initialization phase 0 done
[THREAD] Initialization phase 1 done
[THREAD] Initialization phase 2 done
[THREAD] Initalization completed, notifying main!
[MAIN] Notified! Starting initialization
[MAIN] Initialization phase 0 done
[MAIN] Initialization phase 1 done
[MAIN] Initialization phase 2 done

About the author 

Axel Fortun

​Developer specialized in Linux environment and embedded systems.
​Knowledge in multiple languages as C/C++, Java, Python​ and AngularJs.
​Working as Software developer since 2014 with a beginning in the car industry​ and then in ​medical systems.