Reputation: 29
I'm trying to update a global variable in the main function and have a thread tell me when this variable is positive.
The code: https://pastebin.com/r4DUHaUV
When I run it, only 2 shows up though 1 and 2 should be the correct answer.
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
pthread_t tid;
pthread_mutex_t mtx;
pthread_cond_t cond;
int nr=0;
void* function(void* arg)
{
pthread_mutex_lock(&mtx);
printf("Number in thread : %d \n",nr);
while(nr<=0)
pthread_cond_wait(&cond,&mtx);
printf("Number %d is positive \n",nr);
pthread_mutex_unlock(&mtx);
}
int main()
{
pthread_mutex_init(&mtx,NULL);
pthread_create(&tid,NULL,function,NULL);
int i;
for(i=0;i<3;i++)
{
int isPos=0;
pthread_mutex_lock(&mtx);
if(i==0)
nr=nr+1;
if(i==1)
nr=nr-2;
if(i==2)
nr=nr+3;
if(nr>0)
isPos=1;
if(isPos==1)
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mtx);
}
pthread_join(tid,NULL);
return 0;
}
Upvotes: 2
Views: 2022
Reputation: 66204
As I mentioned in general comment, I'll repeat here:
There is no guarantee the main thread won't go off, locking the mutex, changing nr, signaling the cv (whether or not anyone is actually waiting on it), and unlocking the mutex, all before the child thread even locks the mutex, much less starts waiting on the cv. In that case, nr can be 1 (or 2, etc) when the child finally gets the mutex. That means your while loop will be skipped (nr<=0 is not true), and whatever the current value of nr is will be printed on the way out. I've run this several times, and gotten 1x1, 1x2, and 2x2, multiple times.
A simple fix for this involves using the cv/mtx pair you've set up for monitoring for changes from main
to also monitor startup-start from function
. First the code:
The Code
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int nr = -1;
void* function(void* arg)
{
// signal main to start up once we start waiting
pthread_mutex_lock(&mtx);
nr = 0;
pthread_cond_signal(&cond);
// now start waiting (which will unlock the mutex as well, which means
// the main thread will be be able to acquire it and check nr safely
while(nr<=0)
pthread_cond_wait(&cond,&mtx);
printf("Number %d is positive \n",nr);
pthread_mutex_unlock(&mtx);
return NULL;
}
int main()
{
pthread_t tid;
pthread_create(&tid,NULL,function,NULL);
// wait until child is knowingly waiting
pthread_mutex_lock(&mtx);
while (nr != 0)
pthread_cond_wait(&cond, &mtx);
pthread_mutex_unlock(&mtx);
// now we know the child is ready to receive signals
int i;
for(i=0;i<3;i++)
{
pthread_mutex_lock(&mtx);
if(i==0)
nr=nr+1;
if(i==1)
nr=nr-2;
if(i==2)
nr=nr+3;
int isPos = (nr>0);
pthread_mutex_unlock(&mtx);
if (isPos)
pthread_cond_signal(&cond);
}
pthread_join(tid,NULL);
return 0;
}
How It Works
The initial value of nr
is established as -1
. Only the child thread will change this directly to 0
, and even then only under the protection of the predicate mutex.
// signal main to start up once we start waiting
pthread_mutex_lock(&mtx);
nr = 0;
pthread_cond_signal(&cond);
Note that after the above three lines, the child still owns the mutex. It atomically releases it and begins waiting for notifications with the first entry into the subsequent loop:
while(nr<=0)
pthread_cond_wait(&cond,&mtx);
Now, back in main
, the startup creates the child thread, acquires the mutex, then monitors until nr
is zero.
pthread_create(&tid,NULL,function,NULL);
// wait until child is knowingly waiting
pthread_mutex_lock(&mtx);
while (nr != 0)
pthread_cond_wait(&cond, &mtx);
pthread_mutex_unlock(&mtx);
The only way to make it past this is when nr == 0
. When that happens, the child must have changed it, but more importantly, it also must be waiting on the condition variable (that is how we got the mutex; remember?) From that point on, the code is similar. Worth noting, I use the pthread initializers to ensure the mutex and cvar are properly stood up. Your original post was missing the cvar initialization.
Lastly, doing multiple-predicate double-duty with a single cvar-mtx pair is easy to mess up, and can be very hard to detect edge cases when you did (mess up, that is). Be careful. This specific example is a hand-off sequence of duties, not concurrent duties, making it fairly trivial, so I'm comfortable in showing it.
Hope it helps.
Upvotes: 3