So I am trying to understand pthread_cond_t variables, but the problem is often sometimes pthread_cond_signal()/pthread_cond_broadcast() doesn't work and the sleeping threads are not woken up, leading to a deadlock in my code.
Is there a problem in code? What is the better/best way to use condition variables?
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <pthread.h>
#include <unistd.h>
pthread_mutex_t lock;
pthread_cond_t cv;
int count = 0;
void* routine(){
pthread_mutex_lock(&lock);
while(count!=5) pthread_cond_wait(&cv,&lock);
printf("count value is : %d\n", count);
pthread_mutex_unlock(&lock);
}
void* routine2(){
pthread_mutex_lock(&lock);
for(int i=0; i<7; i++){
count++;
printf("%d\n",count);
if(count == 5) pthread_cond_signal(&cv);
}
pthread_mutex_unlock(&lock);
}
int main(){
pthread_mutex_init(&lock,NULL);
pthread_cond_init(&cv,NULL);
pthread_t t1,t2;
pthread_create(&t1,NULL,&routine,NULL);
pthread_create(&t2,NULL,&routine2,NULL);
pthread_join(t1,NULL);
pthread_join(t2,NULL);
pthread_mutex_destroy(&lock);
pthread_cond_destroy(&cv);
}
The issue is that you're basing your logic on variables that can change between the time you read the variable and the time that you test the value.
In this code:
If
routine()is called first this implicitly means thatcountis 0 (becauseroutine2()is the only function that changescount, and does not have the lock).routine()will callpthread_cond_waitwhich will release the mutex lock and then block until the condition is signaled. Note that it also must be able to obtain the mutex lock before it finishes (i.e just the signal alone isn't sufficient). From pthread_cond_waitIf
routine2()gets the lock first it will iterate untilcountis 5 and then callpthread_cond_signal. This will not however letroutine()continue because the lock is not released at the same time. It will continue iterating through the loop and immediately incrementcountto 6, beforeroutine()ever gets the chance to read from thecountvariable. Ascountcannot possibly be 5 at the timeroutine()resumes, is will deadlock (get stuck doing the above line forever).It might seem like you could avoid this by simply not obtaining the lock with
routine2():However there are also problems with this, as there is no guarantee that
countwill be 5 whenroutine()is read it as there is nothing stoppingroutine2()from continuing to process. If that happensroutine()will again deadlock as count will have been incremented above 5. This is also unadvisable as accessing a shared variable from more than one thread at a time is classified as nondeterministic behavior.The change below resolves the issue with a minimal amount of changes. The one major change is to only wait if
countis less than 5 (if it's 5 or more the signal was already sent).This OnlineGDB snippet demonstrates the potential for deadlock.