Why and how to use condition variables? - Pthreads
- Q: What is a condition variable?
A: A condition variable is a variable of type `pthread_cond_t`. It is
used to suspend the thread execution until some condition is true.
- Q: Is a condition variable dependent on a mutex lock to work properly? Why?
A: Explaining with an example:
Assumptions:- There is a variable `x` whose current value is 0.
- There is a thread `threadA` whose task is to start some job once the
variable `x` reaches the value 1000. - There is a thread `threadB` whose task is to keep on incrementing the
variable `x`'s value until it reaches 1000, and then send a signal
for the threads waiting for this variable's value to turn 1000.
Possible problematic situation:- Scheduler schedules the thread `threadA`.
- Now, the thread `threadA` checks the value of variable `x` and finds
that it hasn't yet reached 10. The thread `A` is now supposed to wait
(by calling a particular pthread function) till that variable's value
reaches 10. - Scheduler schedules the thread `threadB`.
- Thread `threadB` increments the variable `x` changing its value to 10,
and then calls the pthread function responsible for signaling the end
of waiting period for thread `threadA`. - Scheduler schedules the thread `threadA`.
- Thread `threadA` (unaware of the thread `threadB`'s deeds) now starts
waiting for the variable `x` to change its value to 10. The signals of
pthread do _not persist_. The thread `threadA` will now wait forever for
the signal that has already come and gone.
Solution to the above situation:
We need to write a condition such that the thread `threadA` acquires the
mutex lock _before_ checking the value of the variable `x` and
releases it after waiting or deciding not to wait on the condition
variable.
Because the thread `threadA` locks the Mutex _before_ performing the critical
activities (checking the variable's value and then waiting/not-waiting
on the condition variable), the `thread B` simply can't change the
variable's value and or send a signal till the `thread A` unlocks the mutex it
held.
- This code demonstrates the solution for the above problem attained by using
the mutex lock.
Code:
#include <stdlib.h> #include <pthread.h> #include <iostream> /* Mutex variable initialization. */ pthread_mutex_t mutexLock = PTHREAD_MUTEX_INITIALIZER; /* Condition variable initialization. */ pthread_cond_t cond = PTHREAD_COND_INITIALIZER; /* The functions to be executed by `ThreadA` and `ThreadB`. */ void* functionOfThreadA (void * ptr); void* functionOfThreadB (void * ptr); /* * Initialization of the variable on whose value depends the * `ThreadA`'s further execution. */ int x = 0; int main () { /* pthread_t: Used to uniquely identify a thread. */ pthread_t threadA; pthread_t threadB; char *messageA = (char *) "Thread A"; char *messageB = (char *) "Thread B"; int returnValueA; int returnValueB; /* * Arg 1: pthread_t *: Pointer of type `pthread_t` identifying * the unique name of the thread. * * Arg 2: const pthread_attr_t *: Attributes of the thread. * Specifying NULL means setting default attributes. * * Arg 3: void *(*start_routine) (void *): Function pointer * corresponding to the function to be executed by the * thread. * * Arg 4: void* : Argument of the function to be executed by * the thread. */ returnValueA = pthread_create (&threadA, NULL, functionOfThreadA, (void*) messageA); returnValueB = pthread_create (&threadB, NULL, functionOfThreadB, (void*) messageB); /* * We want the creater thread to wait till all the spawned * threads complete their tasks. * * Arg 1: pthread_t: Thread ID of type `pthread_t` identifying * the unique name of the thread. * * Arg 2: void**: If not NULL will contain the return value of * the function `pthread_join`. */ pthread_join (threadA, NULL); pthread_join (threadB, NULL); } void * functionOfThreadA (void * ptr) { char * message; message = (char *) ptr; std :: cout << message << " has been scheduled now.\n"; /* * The `threadA` will continiously (unless the scheduler * deschedules it) keep an eye on the critical section. */ while (1) { /* * `threadA` can lock the mutex only if `threadB` * hasn't locked it yet. This means that `threadB` can * increment the variable `x` and/or send the signal * only after `threadA` has started waiting or has * checked the value of the variable `x` and found it * as desired. * * Arg 1: pthread_mutex_t *: Mutex variable to be * locked. */ pthread_mutex_lock (&mutexLock); /* Is the condition satisfied? */ if (x == 1000) { std :: cout << "\nA: x is now 1000. Waiting period over."<< "\n"; return 0; } /* If not, then the `threadA` will start `waiting`. */ else { std :: cout << "\nNow, thread A will wait for value of `x` to reach 1000" << "\n"; /* * Arg 1: pthread_cond_t *: The condition * variable shared within the concerned * threads. * * Arg 2: pthread_mutex_t *: The mutex * variable shared within the concerned * threads. */ pthread_cond_wait (&cond, &mutexLock); } /* * After the `threadA` starts waiting or decides not * to wait, the mutex will be unlocked. * * Arg 1: pthread_mutex_t *: The mutex variable to be * unlocked. */ pthread_mutex_unlock (&mutexLock); } } void * functionOfThreadB (void * ptr) { char * message; message = (char *) ptr; std :: cout << message << " has been scheduled now.\n"; /* * The `threadB` will continiously (unless the scheduler * deschedules it) keep an eye on the critical section. */ while (1) { /* * `threadB` can lock the mutex only if `threadA` * hasn't locked it yet. This means that `threadB` can * increment the variable `x` and/or send the signal * only when `threadA` is NOT in its critical section. * * This way either `threadA` will receive the signal * (if it is already waiting) or when it enters its * critical section it'll simply find the variable * incremented to the desired value (thus will not * need to wait). */ pthread_mutex_lock (&mutexLock); x = x + 1; /* Is the condition satisfied? */ if (x == 1000) { std :: cout << "\nB: Signaled. Value of x: " << x << "\n"; /* * If yes, then send the signal to the waiting * thread(s). * * Arg 1: pthread_cond_signal *: Condition * variable w.r.t to whom the signal is * supposed to be sent. */ pthread_cond_signal (&cond); /* Unlock the mutex and return. */ pthread_mutex_unlock (&mutexLock); return 0; } /* If not, then do nothing other than... */ else { std :: cout << "\nB: Not signaled yet. Value of x: " << x << "\n"; } /* * simply unlock the mutex and get out of the * critical section. */ pthread_mutex_unlock (&mutexLock); } }
Code:
anisha@linux-trra:~> g++ conditionVariablesDemo.cpp -pthread anisha@linux-trra:~> ./a.out Thread B has been scheduled now. B: Not signaled yet. Value of x: 1 B: Not signaled yet. Value of x: 2 B: Not signaled yet. Value of x: 3 B: Not signaled yet. Value of x: 4 B: Not signaled yet. Value of x: 5 B: Not signaled yet. Value of x: 6 B: Not signaled yet. Value of x: 7 B: Not signaled yet. Value of x: 8 B: Not signaled yet. Value of x: 9 B: Not signaled yet. Value of x: 10 B: Not signaled yet. Value of x: 11 . . . . B: Not signaled yet. Value of x: 384 B: Not signaled yet. Value of x: 385 B: Not signaled yet. Value of x: 386 B: Not signaled yet. Value of x: 387 B: Not signaled yet. Value of x: 388 B: Not signaled yet. Value of x: 389 Thread A has been scheduled now. Now, thread A will wait for value of `x` to reach 1000 B: Not signaled yet. Value of x: 390 B: Not signaled yet. Value of x: 391 B: Not signaled yet. Value of x: 392 B: Not signaled yet. Value of x: 393 B: Not signaled yet. Value of x: 394 . . . . B: Not signaled yet. Value of x: 986 B: Not signaled yet. Value of x: 987 B: Not signaled yet. Value of x: 988 B: Not signaled yet. Value of x: 989 B: Not signaled yet. Value of x: 990 B: Not signaled yet. Value of x: 991 B: Not signaled yet. Value of x: 992 B: Not signaled yet. Value of x: 993 B: Not signaled yet. Value of x: 994 B: Not signaled yet. Value of x: 995 B: Not signaled yet. Value of x: 996 B: Not signaled yet. Value of x: 997 B: Not signaled yet. Value of x: 998 B: Not signaled yet. Value of x: 999 B: Signaled. Value of x: 1000 A: x is now 1000. Waiting period over. anisha@linux-trra:~>
Total Comments 0