POSIX C Threads. pthread_cond_t example. Doesn't work as expected
Asked Answered
R

4

6

I wrote a wrote a program and it doesn't work as I expect it to. I have two threads: thread triggers func and anotherThread triggers anotherFunc. What I wanted to do is when cont reaches value 10 in func, anotherThread to be triggered using pthread_cond_wait and pthread_cond_signal. The strange thing is everything works fine if i uncomment the sleep(1) line. I'm new to threads and I was following the tutorial here and if I comment the sleep line in their example it breaks as well.

My question is how can I make this work without any sleep() calls? And what happens if in my code both func reaches pthread_mutex_lock after anotherFunc? How can I control these things? This is my code:

#include <iostream>
#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>

pthread_mutex_t myMutex;
pthread_cond_t cond;
pthread_attr_t attr;

int cont;

void *func(void*)
{
    printf("func\n");

    for(int i = 0; i < 20; i++)
    {
        pthread_mutex_lock(&myMutex);

        cont++;
        printf("%d\n", cont);
        if(cont == 10)
        {
            printf("signal:\n");
            pthread_cond_signal(&cond);
//            sleep(1);
        }
        pthread_mutex_unlock(&myMutex);
    }
    printf("Done func\n");

    pthread_exit(NULL);
}

void *anotherFunc(void*)
{
    printf("anotherFunc\n");
    pthread_mutex_lock(&myMutex);
    printf("waiting...\n");

    pthread_cond_wait(&cond, &myMutex);
    cont += 10;
    printf("slot\n");

    pthread_mutex_unlock(&myMutex);
    printf("mutex unlocked anotherFunc\n");
    printf("Done anotherFunc\n");

    pthread_exit(NULL);
}

int main(int argc, char *argv[])
{
    pthread_t thread;
    pthread_t anotherThread;

    pthread_attr_init(&attr);
    pthread_mutex_init(&myMutex, NULL);
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
    pthread_cond_init(&cond, NULL);

    pthread_create(&anotherThread, &attr, anotherFunc, NULL);
    pthread_create(&thread, &attr, func, NULL);

    pthread_join(thread, NULL);
    pthread_join(anotherThread, NULL);

    printf("Done MAIN()");

    pthread_mutex_destroy(&myMutex);
    pthread_cond_destroy(&cond);
    pthread_attr_destroy(&attr);


    pthread_exit(NULL);
    return 0;
}

Sorry for the long post but I'm new to threads and I'm willing to learn. Also do you know some good references or courses/tutorials on threads and networking on Linux? I want to learn create an chat client and I heard that I have to know threads and networking for that. Problem is I don't know pretty good if what I learn is ok since I don't know what I have to know.

Thank you so much :)

Robbirobbia answered 16/4, 2012 at 18:27 Comment(2)
Other remarks. I addition to not having to use a pthread_attr_t, you can use static initializers for the condition and mutex to simplify the code. Furthermore, your thread functions can just return NULL; or return 0; instead of calling pthread_exit. And your main thread can just return 0; from main without calling pthread_exit. The other threads are not running at that point since they were joined. return from main forces a process exit, but pthread_exit in the primary thread does not force a process exit, which is useful for keeping other threads running.Shemikashemite
Also, there is no need to clean up static mutexes and conditions. Just use pthread_mutex_t myMutex = PTHREAD_MUTEX_INITIALIZER;. The complex initializations are needed for situations where you want to set unusual attributes. E.g. make a process-shared robust mutex.Shemikashemite
S
9

Your anotherThread simply calls pthread_cond_wait without first testing whether the desired condition (counter reaching ten) has already happened. This is incorrect logic, which will lead to the lost wakeup problem: the name of a recurring bug that plagues incorrectly written multithreaded programs.

Condition variables are stateless. If pthread_cond_signal or pthread_cond_broadcast is called on a condition on which no threads are currently waiting, the operation has no effect. It is not remembered. So it is possible for your signaling thread to count to 10 very quickly, and signal the condition variable, before the other thread has reached the pthread_cond_wait call.

You need a loop around pthread_cond_wait. The condition must be checked in case it is already true, so that the thread does not wait for a wakeup which already happened. And it should be a loop because wakeups can be spurious: just because the thread falls through the pthread_cond_wait doesn't mean that the condition is actually true:

while (cont < 10)
  pthread_cond_wait(&cond, &myMutex);

Also, there is no need to create a thread attribute just to make a thread joinable. This is the default situation when you use a null pointer for the creation attribute. POSIX threads are joinable unless created detached, or converted to detached with pthread_detach.

Another thing: whenever possible, avoid calling pthread_cond_signal while holding a mutex lock. It's not incorrect, but it's potentially wasteful, because the operation may actually have to call into the OS kernel to wake up a thread, and so you're holding this expensive mutex lock over the entire system call (when all you really need it for is protecting a few machine instructions that working with shared data in your application).

Shemikashemite answered 16/4, 2012 at 18:36 Comment(6)
Your anotherThread simply calls pthread_cond_wait without first testing whether the desired condition (counter reaching ten) has already happened I thought condition variables work like this: when execution reaches pthread_cond_wait, the thread freezes until the signal is received. why do i have to test weather the desired condition has already happened since this is the condition variable's task? i mean...why do i need condition variables since i could just do a loop to test if my condition is reached and create a flag for this for example. and when that flag is true, the other thread exits.Robbirobbia
I tried to put the pthread_cond_wait in the loop as you said but it still doesn't work... pastebin.com/tNB9wCMW i'm very confused because i don't understand how can you control the threads so execution reaches pthread_cond_wait before pthread_cond_signal Thank you for your answersRobbirobbia
what happens actually when execution reaches pthread_cond_wait. does it freeze until the signal is received? if yes, why do i need a while loop and not just an if statement?Robbirobbia
if execution reaches pthread_cond_signal befor pthread_cond_wait, what is the effect of the while loop, since the execution passed pthread_cond_signal anyway and it won't trigger the signal anymore?Robbirobbia
If execution reaches the signal first, then that signal will have no effect, since no threads are waiting on the condition. But no wait is subsequently required, because the counter is already at the desired value.Shemikashemite
I.e. if the while loop is reached in that situation, the guard condition is already true and so the body with the condition wait is skipped.Shemikashemite
E
2

I don't know what your actual problem is (what happens when it doesn't work?)..

I see one major problem, you don't handle the spurious wakeups.

You need something that signals that the condition really true, for example with a boolean variable:

init:

signaled = false;

signal:

signaled = true;
pthread_cond_signal(&cond);

receive:

while (!signaled)
   pthread_cond_wait(&cond, &myMutex);
Education answered 16/4, 2012 at 18:38 Comment(1)
The major problem is the lost wakeup, for which you need an if around the pthread_cond_wait. Spurious wakeups are a minor problem which requires that to be a while. It's unlikely that a spurious wakeup will even happen in this one-shot situation with just two threads.Shemikashemite
W
1

What you want is a semaphore not a condition variable.

A semaphore maintains state and counts wait() and signals() against it.
It is usually implemented using a condition variable.

Look here for a trivial implementation.

Wish answered 16/4, 2012 at 18:55 Comment(0)
I
0

This is just an idea but you could also use semaphores to do the "trigger". I remember a project I work on a while ago and what we did is while one of our thread was waiting to be triggered (using a sem_wait) it would simply wait for an infinite time trying to get the lock (and thus was been taken off the proc by the scheduler and saving precious cycles). Once the main thread was done doing his calculation, it would release the semaphore allowing the second thread to do it's calculation.

This is simply a different alternative.

Isacco answered 16/4, 2012 at 18:52 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.