Understanding of Pthread_Cond_Wait() and Pthread_Cond_Signal()

understanding of pthread_cond_wait() and pthread_cond_signal()

pthread_cond_signal does not unlock the mutex (it can't as it has no reference to the mutex, so how could it know what to unlock?) In fact, the signal need not have any connection to the mutex; the signalling thread does not need to hold the mutex, though for most algorithms based on condition variables it will.

pthread_cond_wait unlocks the mutex just before it sleeps (as you note), but then it reaquires the mutex (which may require waiting) when it is signalled, before it wakes up. So if the signalling thread holds the mutex (the usual case), the waiting thread will not proceed until the signalling thread also unlocks the mutex.

The common use of condition vars is something like:

thread 1:
pthread_mutex_lock(&mutex);
while (!condition)
pthread_cond_wait(&cond, &mutex);
/* do something that requires holding the mutex and condition is true */
pthread_mutex_unlock(&mutex);

thread2:
pthread_mutex_lock(&mutex);
/* do something that might make condition true */
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);

The two threads have some shared data structure that the mutex is protecting access to. The first thread wants to wait until some condition is true then immediately do some operation (with no race condition opportunity for some other thread to come in between the condition check and action and make the condition false.) The second thread is doing something that might make the condition true, so it needs to wake up anyone that might be waiting for it.

understanding pthread_cond_wait() and pthread_cond_signal()

In practical terms, only one thread is awakened and you can't control which one it is.

(pthread_cond_signal wakes up at least one thread waiting on the given condition variable, and the thread chosen is determined by scheduling policy.)

In your case, you need to reconsider what the "condition" represented by your condition variable (condvar) means.

If the condvar truly means "a producer has added an item to one of several queues, each of which has a dedicated consumer," then you should pthread_cond_broadcast to awaken each queue's consumer and let the awakened threads figure out if there is work to do. Alternatively, you might recast the condition as "a producer has added an item to this queue, which has a dedicated consumer," and use one condvar per queue.

Trying to understand pthread_cond_lock and pthread_cond_signal

First, a summary:

  • pthread_mutex_lock(&mutex):

    If mutex is free, then this thread grabs it immediately.

    If mutex is grabbed, then this thread waits until the mutex becomes free, and then grabs it.
     

  • pthread_mutex_trylock(&mutex):

    If mutex is free, then this thread grabs it.

    If mutex is grabbed, then the call returns immediately with EBUSY.
     

  • pthread_mutex_unlock(&mutex):

    Releases mutex.
     

  • pthread_cond_signal(&cond):

    Wake up one thread waiting on the condition variable cond.
     

  • pthread_cond_broadcast(&cond):

    Wake up all threads waiting on the condition variable cond.
     

  • pthread_cond_wait(&cond, &mutex):

    This must be called with mutex grabbed.

    The calling thread will temporarily release mutex and wait on cond.

    When cond is broadcast on, or signaled on and this thread happens to be the one woken up, then the calling thread will first re-grab the mutex, and then return from the call.

    It is important to note that at all times, the calling thread either has mutex grabbed, or is waiting on cond. There is no interval in between.


Let's look at a practical, running example code. We'll create it along the lines of OP's code.

First, we'll use a structure to hold the parameters for each worker function. Since we'll want the mutex and the condition variable to be shared between threads, we'll use pointers.

#define  _POSIX_C_SOURCE  200809L
#include <stdlib.h>
#include <pthread.h>
#include <limits.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>

/* Worker function work. */
struct work {
pthread_t thread_id;
pthread_mutex_t *lock; /* Pointer to the mutex to use */
pthread_cond_t *wait; /* Pointer to the condition variable to use */
volatile int *done; /* Pointer to the flag to check */
FILE *out; /* Stream to output to */
long id; /* Identity of this thread */
unsigned long count; /* Number of times this thread iterated. */
};

The thread worker function receives a pointer to the above structure. Each thread iterates the loop once, then waits on the condition variable. When woken up, if the done flag is still zero, the thread iterates the loop. Otherwise, the thread exits.

/* Example worker function. */
void *worker(void *workptr)
{
struct work *const work = workptr;

pthread_mutex_lock(work->lock);

/* Loop as long as *done == 0: */
while (!*(work->done)) {
/* *(work->lock) is ours at this point. */

/* This is a new iteration. */
work->count++;

/* Do the work. */
fprintf(work->out, "Thread %ld iteration %lu\n", work->id, work->count);
fflush(work->out);

/* Wait for wakeup. */
pthread_cond_wait(work->wait, work->lock);
}

/* *(work->lock) is still ours, but we've been told that all work is done already. */
/* Release the mutex and be done. */
pthread_mutex_unlock(work->lock);
return NULL;
}

To run the above, we'll need a main() as well:

#ifndef  THREADS
#define THREADS 4
#endif

int main(void)
{
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t wait = PTHREAD_COND_INITIALIZER;
volatile int done = 0;
struct work w[THREADS];

char *line = NULL, *p;
size_t size = 0;
ssize_t len = 0;

unsigned long total;
pthread_attr_t attrs;
int i, err;

/* The worker functions require very little stack, but the default stack
size is huge. Limit that, to reduce the (virtual) memory use. */
pthread_attr_init(&attrs);
pthread_attr_setstacksize(&attrs, 2 * PTHREAD_STACK_MIN);

/* Grab the mutex so the threads will have to wait to grab it. */
pthread_mutex_lock(&lock);

/* Create THREADS worker threads. */
for (i = 0; i < THREADS; i++) {

/* All threads use the same mutex, condition variable, and done flag. */
w[i].lock = &lock;
w[i].wait = &wait;
w[i].done = &done;

/* All threads output to standard output. */
w[i].out = stdout;

/* The rest of the fields are thread-specific. */
w[i].id = i + 1;
w[i].count = 0;

err = pthread_create(&(w[i].thread_id), &attrs, worker, (void *)&(w[i]));
if (err) {
fprintf(stderr, "Cannot create thread %d of %d: %s.\n", i+1, THREADS, strerror(errno));
exit(EXIT_FAILURE); /* Exits the entire process, killing any other threads as well. */
}
}

fprintf(stderr, "The first character on each line controls the type of event:\n");
fprintf(stderr, " e, q exit\n");
fprintf(stderr, " s signal\n");
fprintf(stderr, " b broadcast\n");
fflush(stderr);

/* Let each thread grab the mutex now. */
pthread_mutex_unlock(&lock);

while (1) {
len = getline(&line, &size, stdin);
if (len < 1)
break;

/* Find the first character on the line, ignoring leading whitespace. */
p = line;
while ((p < line + len) && (*p == '\0' || *p == '\t' || *p == '\n' ||
*p == '\v' || *p == '\f' || *p == '\r' || *p == ' '))
p++;

/* Do the operation mentioned */
if (*p == 'e' || *p == 'E' || *p == 'q' || *p == 'Q')
break;
else
if (*p == 's' || *p == 'S')
pthread_cond_signal(&wait);
else
if (*p == 'b' || *p == 'B')
pthread_cond_broadcast(&wait);
}

/* It is time for the worker threads to be done. */
pthread_mutex_lock(&lock);
done = 1;
pthread_mutex_unlock(&lock);

/* To ensure all threads see the state of that flag,
we wake up all threads by broadcasting on the condition variable. */
pthread_cond_broadcast(&wait);

/* Reap all threds. */
for (i = 0; i < THREADS; i++)
pthread_join(w[i].thread_id, NULL);

/* Output the thread statistics. */
total = 0;
for (i = 0; i < THREADS; i++) {
total += w[i].count;
fprintf(stderr, "Thread %ld: %lu events.\n", w[i].id, w[i].count);
}
fprintf(stderr, "Total: %lu events.\n", total);

return EXIT_SUCCESS;
}

If you save the above as example.c, you can compile it to example using e.g. gcc -Wall -O2 example.c -lpthread -o example.

To get the correct intuitive grasp of the operations, run the example in a terminal, with the source code in a window next to it, and see how the execution progresses as you provide input.

You can even run commands like printf '%s\n' s s s b q | ./example to run a sequence of events in a quick succession, or printf 's\ns\ns\nb\nq\n' | ./example with even less time in between events.

After some experimentation, you'll hopefully find out that not all input events cause their respective action. This is because the exit event (q above) is not synchronous: it does not wait for all pending work to be done, but tells the threads to exit right then and there. That is why the number of events may vary even for the exact same input.

(Also, if you signal on the condition variable, and immediately broadcast on it, the threads tend to only get woken up once.)

You can mitigate that by delaying the exit, using e.g. (printf '%s\n' s s b s s s ; sleep 1 ; printf 'q\n' ) | ./example.

However, there are better ways. A condition variable is not suitable for countable events; it is really flag-like. A semaphore would work better, but then you should be careful to not overflow the semaphore; it can only be from 0 to SEM_VALUE_MAX, inclusive. (So, you could use a semaphore to represent the number of pending job, but probably not for the number of iterations done by each/all thread workers.) A queue for the work to do, in thread pool fashion, is the most common approach.

What happens to a thread calling pthread_cond_signal?

The thread that calls pthread_cond_signal returns immediately. It does not wait for the woken thread (if there is one) to do anything.

If you call pthread_cond_signal while holding the mutex that the blocked thread is using with pthread_cond_wait, then the blocked thread will potentially wake from the condition variable wait, then immediately block waiting for the mutex to be acquired, since the signalling thread still holds the lock.

For the best performance, you should unlock the mutex prior to calling pthread_cond_signal.

Also, pthread_cond_wait may return even though no thread has signalled the condition variable. This is called a "spurious wake". You typically need to use pthread_cond_wait in a loop:

pthread_mutex_lock(&mut);
while(!ready){
pthread_cond_wait(&cond,&mut);
}
// do stuff
pthread_mutex_unlock(&mut);

The signalling thread then sets the flag before signalling:

pthread_mutex_lock(&mut);
ready=1
pthread_mutex_unlock(&mut);
pthread_cond_signal(&cond);

What is the pthread_cond_wait() & pthread_cond_signal() calling sequence?

If you have a sequential task don't use threads:

#include <stdio.h>

char name[14];

int main() {
printf("Enter Name\n");
scanf("%s",name);
printf("Name: %s\n", name);
return 0
}

You can use a state variable to ensure that entry() run before display():

#include <errno.h>
#include <pthread.h>
#include <stdio.h>
#include <string.h>

pthread_mutex_t lockid;
pthread_cond_t cvar = PTHREAD_COND_INITIALIZER;
char name[14];
enum { START, ENTRY } state = START;
#define CHECK(e) { int error = (e); if(error) printf("%s:%d %s", __FILE__, __LINE__, strerror(error)); }
#define CHECK2(p, e) { if(p) CHECK(e); }


void *entry() {
CHECK(pthread_mutex_lock(&lockid));
printf("Enter Name\n");
CHECK2(scanf("%s", name) == EOF, errno);
state = ENTRY;
pthread_cond_signal(&cvar);
CHECK(pthread_mutex_unlock(&lockid));
return 0;
}

void *display() {
CHECK(pthread_mutex_lock(&lockid));
while(state != ENTRY) pthread_cond_wait(&cvar, &lockid);
printf("Name: %s\n", name);
CHECK(pthread_mutex_unlock(&lockid));
return 0;
}

int main() {
pthread_mutex_init(&lockid, NULL);
pthread_t id1, id2;
CHECK(pthread_create(&id1, NULL, &entry, NULL));
CHECK(pthread_create(&id2, NULL, &display, NULL));
CHECK(pthread_join(id1,NULL));
CHECK(pthread_join(id2,NULL));
return 0;
}

About pthread_cond_signal and pthread_cond_wait

pthread_cond_wait() unlocks the mutex on entry and locks it again on exit. If another thread acquires the lock during that time, pthread_cond_wait() cannot return until that other thread has released the lock.

So, if watch_count() is blocked in pthread_cond_wait(), and inc_count() runs and calls pthread_cond_signal(), then watch_count() will not return from pthread_cond_wait() until inc_count() has called pthread_mutex_unlock().

However, pthread_cond_wait() can wake even if not signalled. This is called a spurious wake-up. watch_count() can therefore execute count+=125 many times, even if inc_count() never runs, or never calls pthread_cond_signal().

pthread_cond_signal doesn't unblock pthread_cond_wait thread immediately

This is the intended behavior. pthread_cond_signal does not yield it's remaining runtime, but will continue to run.

And yes, pthread_cond_signal will immediately unblock (one or more) thread waiting on the corresponding condition variable. However, that doesn't guarantee that said waiting thread will immediately run. It just tells the OS that this thread is no longer blocked, and it's up to the OS thread scheduler to decide when to start running it. Since the signalling thread is already running, is hot in the cache etc., it will likely have plenty of time to do something before the now-unblocked thread starts doing anything.

In your example above, if you don't want to skip messages, maybe what you're looking for is something like a producer-consumer queue, maybe backed by a ring buffer.



Related Topics



Leave a reply



Submit