Search code examples
multithreadingpthreadscondition-variable

Trigger multiple pthreads by pthread_cond_broadcast


Since the examples for pthreads with pthread_cond_broadcast wakeup are sparse i wrote one, but are unsure if this is correctly synchronized and the way to do it:

  1. do all threads share the same c and mtx variable?
  2. is it necessary upon pthread_cond_wait return to test if some condition is actually met? in my case every call to broadcast should wake up every thread exactly once, but no-one else should do so. (do i prevent spurious wakeups?)
  3. the program currently does not exit despite async cancel type. also no success with deferred cancellation tried in example code despite pthread_cond_wait being a cancellation point so.

overall does it work like i expect it to.


 #include <pthread.h> 
    #include <iostream>
    #include <unistd.h>

    struct p_args{
        int who;
    };

    pthread_cond_t  c;      //share between compilation units
    pthread_mutex_t mtx;

    void *threadFunc(void *vargs){
        //pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,NULL);
        struct p_args * args = (struct p_args *) vargs;

        while(true){
            //wait for trigger one loop
            pthread_mutex_lock(&mtx);
            pthread_cond_wait(&c, &mtx);
            pthread_mutex_unlock(&mtx);
            //should be entangled output showing concurrent execution
            std::cout << "t " << args->who << std::endl;

            /* expensive work */
        }
        delete args;
    }

    int main(int argc, char* argv[])
    {
        pthread_cond_init(&c, NULL);
        pthread_mutex_init(&mtx, NULL);

        pthread_t thread_id[2];

        struct p_args *args0 = new p_args();
        struct p_args *args1 = new p_args();
        args0->who = 0;
        args1->who = 1;

        pthread_create(&thread_id[0], NULL,  threadFunc,    args0);
        pthread_create(&thread_id[1], NULL,  threadFunc,    args1);

        sleep(3);
        pthread_mutex_lock(&mtx);
        pthread_cond_broadcast(&c);
        pthread_mutex_unlock(&mtx);
        sleep(3);//test if thread waits  
        pthread_cancel(thread_id[0]);
        pthread_cancel(thread_id[1]);

        pthread_join (thread_id[0], NULL);
        pthread_join (thread_id[1], NULL);
        //could perform cleanup here
        return 0;
    }

Regarding exiting deferred:
thread_id[0] exits fine and i am stuck in line `pthread_join (thread_id[1], NULL);`, it says (Exiting) but seems stuck on a lock, with debugger:  
<br>
[![enter image description here][2]][2]
<br>

EDIT final solution i came up with:

#include <pthread.h>
#include <iostream>
#include <unistd.h>

struct p_args{
    int who;
};

pthread_cond_t  c;
pthread_mutex_t mtx;
bool doSome[2];
bool exitFlag;

void *threadFunc(void *vargs){
    struct p_args * args = (struct p_args *) vargs;

    while(true){
        //wait for trigger one loop
        pthread_mutex_lock(&mtx);
        do {
            pthread_cond_wait(&c, &mtx);
            if(exitFlag) {
                std::cout << "return " << args->who << std::endl;
                delete args;
                pthread_mutex_unlock(&mtx);
                return NULL;
            }
        } while(doSome == false);
        doSome[args->who] = false;
        pthread_mutex_unlock(&mtx);
        std::cout << "t " << args->who << std::endl;
    }
}

int main(int argc, char* argv[])
{
    pthread_cond_init(&c, NULL);
    pthread_mutex_init(&mtx, NULL);
    pthread_t thread_id[2];

    struct p_args *args0 = new p_args();
    struct p_args *args1 = new p_args();
    args0->who = 0;
    args1->who = 1;

    doSome[0] = doSome[1] = true;
    exitFlag = false;
    pthread_create(&thread_id[0], NULL,  threadFunc,    args0);
    pthread_create(&thread_id[1], NULL,  threadFunc,    args1);

    doSome[0] = doSome[1] = true;
    pthread_cond_broadcast(&c);
    sleep(3);
    doSome[0] = doSome[1] = true;
    pthread_cond_broadcast(&c);
    sleep(3);
    exitFlag = true;
    pthread_cond_broadcast(&c);

    pthread_join (thread_id[0], NULL);
    pthread_join (thread_id[1], NULL);

    return 0;
}

Solution

    1. do all threads share the same c and mtx variable?

    Yes, just like any other global variable. You could print their addresses from each thread to confirm it.

    1. is it necessary upon pthread_cond_wait return to test if some condition is actually met?

    Yes, all wait interfaces are subject to spurious wakeups, and you're always responsible for checking your own predicate. See the documentation or a good book.

    1. the program currently does not exit ...

    pthread_cancel is uniformly horrible and should never be used. It's really hard to get right. If you want to tell your thread to exit, write a notification mechanism - build it into the existing predicate loop - and signal/broadcast to make sure all threads wake up and realize it's time to die.

    Regarding exiting deferred: thread_id[0] exits fine and i am stuck in line pthread_join (thread_id[1], NULL);, it says (Exiting) but seems stuck on a lock

    One of the hard things about pthread_cancel is cleanup. If cancellation occurs while you're holding a lock, you need to have used pthread_cleanup_push to emulate cancel-compatible RAII semantics. Otherwise the first thread may (and in this case, did) die with the mutex still locked.

    In this case the second thread is trying to exit from pthread_const_wait due to cancellation, but it needs to regain the lock and can't.


    The usual form of a condition variable loop is this (and a good reference book should show something similar):

    void *thread(void *data)
    {
        struct Args *args = (struct Args *)data;
    
        /* this lock protects both the exit and work predicates.
         * It should probably be part of your argument struct,
         * globals are not recommended.
         * Error handling omitted for brevity,
         * but you should really check the return values.
         */
        pthread_mutex_lock(&args->mutex);
    
        while (!exit_predicate(args)) {
            while (!work_predicate(args)) {
                /* check the return value here too */
                pthread_cond_wait(&args->condition, &args->mutex);
            }
            /* work_predicate() is true and we have the lock */
            do_work(args);
        }
    
        /* unlock (explicitly) only once.
         * If you need to cope with cancellation, you do need
         * pthread_cleanup_push/pop instead.
         */
        pthread_mutex_unlock(&args->mutex);
    
        return data;
    }
    

    where your custom code can just go in bool exit_predicate(struct Args*), bool work_predicate(struct Args*) and void do_work(struct Args*). The loop structure itself rarely needs much alteration.