2
votes

I'm having trouble understanding condition variables like pthread_mutex_lock/unlock and pthread_cond_wait/signal

I'm trying to create nine threads, and have them all run at the same time to figure out which is the most efficient.

    int threadNumber = 0;
    pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
    pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

    #define NUM_THREADS    9

    //used to store the information of each thread
    typedef struct{
        pthread_t  threadID;
        int policy;
        struct sched_param param;
        long startTime;
        long taskStartTime;
        long endTime1;
        long endTime2;
        long endTime3;
        long runTime;
        char startDate[30];
        char endDate[30];
    }ThreadInfo;

ThreadInfo myThreadInfo[NUM_THREADS];



//main function
int main(void){

   printf("running...\n");

   pthread_mutex_lock(&mutex); //lock the mutex//////
   pthread_cond_wait(&cond, &mutex); //start waiting//////


   int fifoPri = 60;
   int rrPri = 30;

   //create the 9 threads and assign their scheduling policies
   for(int i=0; i<NUM_THREADS; i++){

      if(i%3 == SCHED_OTHER){
            myThreadInfo[i].policy = SCHED_OTHER;
            myThreadInfo[i].param.sched_priority = 0;

      }
      else if (i%3 == SCHED_RR){ 
            myThreadInfo[i].policy = SCHED_RR;
            myThreadInfo[i].param.sched_priority = rrPri++; 
      }

      else{
            myThreadInfo[i].policy = SCHED_FIFO; 
            myThreadInfo[i].param.sched_priority = fifoPri++; 

      }

      pthread_create( &myThreadInfo[i].threadID, NULL, ThreadRunner, &myThreadInfo[i]);

   }

   printf("\n\n");

   pthread_mutex_unlock(&mutex) //unlock the mutex/////////
   pthread_cond_signal(&cond); //signal the threads to start////////


   //join each thread
   for(int g = 0; g < NUM_THREADS; g++){
      pthread_join(myThreadInfo[g].threadID, NULL);
   }


   //print out the stats for each thread and perform an analysis of the data
   DisplayThreadSchdStats();


   return 0;
}

...

so when the main function starts, I lock the mutex to make sure the threads don't start before I tell them to with pthread_lock(&mutex) and pthread_cond_wait(&cond, &mutex)

then I create all nine threads with various scheduling policies. After that's completely done, I try to tell the threads to all start at the same time using pthread_mutex_unlock(&mutex) and pthread_cond_signal(&cond)

But when I run this, it never unlocks the threads. The main function's "running..." print statement goes off, but the threads never start. (threadrunner has a function where they all print out a ton of different numbers so I can see if they launch). What am I doing wrong with the pthread mutex and pthread cond?

3
Perhaps what you are looking for is pthread_cond_broadcastPablo
Consider also pthread_barrier_wait and related functions.Basile Starynkevitch
pthread_mutex_* is the basic blocking mechanism. The pthread_cond_* functions are to be used to test arbitrary conditions on a mutex. They depend on a mutex to block, and you need to pass a mutex reference for them to be used.... They are different things.... not different things to implement the same thing.Luis Colorado

3 Answers

3
votes

The accepted answer is "good enough" for the purposes of makeshift benchmarking. However, it uses several problematic practices: condition variables without predicates, and sleep() as ersatz synchronization.

Much better, and just as easy, would be to use a pthreads barrier object.

#define NUM_THREADS ...

static pthread_barrier_t bar;

static void*
thrfunc(void *arg) {
  // set scheduling policy

  pthread_barrier_wait(&bar); // wait till all peers have also done so

  ...
}

int
main(void) {
  pthread_barrier_init(&bar, NULL, NUM_THREADS); // FIXME: error check

  for (int i = 0; i < NUM_THREADS; i++) {
    // spawn threads with various scheduling policy instructions
  }

  // join and tabulate results

  pthread_barrier_destroy(&bar);

  ...
}

Addendum

But what to do if your platform doesn't support barriers? In this case, you can implement a "one-use barrier," which waits for N parties to arrive.

Your main thread would init_quorum(NUM_THREADS) and the workers would await_quorum(...), advancing only when all of them were ready.

struct quorum {
  pthread_cond_t  cond;
  pthread_mutex_t mut;
  size_t          count;  // How many are needed?
};

void
await_quorum(struct quorum *q) {  // FIXME: error checking
  pthread_mutex_lock(&q->mut);

  if (q->count > 0) {
    q->count--;

    if (q->count == 0) {
      // I'm the last needed; we have quorum
      pthread_cond_broadcast(&q->cond);
    } else {
      // Wait for sufficient threads to arrive
      while (q->count > 0)
        pthread_cond_wait(&q->cond, &q->mut);
    }
  } // else quorum already achieved; I'm late!

  pthread_mutex_unlock(&q->mut);
}
2
votes

From the pthread_cond_wait() man page:

The pthread_cond_wait() function atomically blocks the current thread waiting on the condition variable specified by cond, and releases the mutex specified by mutex.

... so when your main() function gets to this line:

pthread_cond_wait(&cond, &mutex); //start waiting//////

... it will, as your comment suggests, start waiting until someone signals the condition variable.

But there are no other threads spawned yet, so there is nobody out there to signal the condition variable, and hence your main thread blocks indefinitely inside that call.

1
votes

I think what you want to do is something like this. I use pthread_cond_broadcast to awake all threads at once:

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <time.h>
#include <sys/time.h>


pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

struct data_t {
    int id;
    int sleep;
};

void* thread(void *arg)
{
    struct data_t *data = arg;


    pthread_mutex_lock(&mutex);
    printf("Thread %d: waiting for release\n", data->id);

    pthread_cond_wait(&cond, &mutex);
    pthread_mutex_unlock(&mutex); // unlocking for all other threads

    struct timeval tv;
    gettimeofday(&tv, NULL);    

    printf("Thread %d: doing some work for %d secs, started: %ld...\n", data->id, data->sleep, tv.tv_sec);
    sleep(data->sleep);
    gettimeofday(&tv, NULL);    
    printf("Thread %d: Bye, end at %ld\n", data->id, tv.tv_sec);
}

int main(void)
{

    struct data_t data[9];
    pthread_t ths[9];

    srand(time(NULL));

    for(int i = 0; i < 9; ++i)
    {
        data[i].id = i + 1;
        data[i].sleep = 1 + rand() % 6;

        pthread_create(ths + i, NULL, thread, data + i);
    }

    // give time for all threads to lock
    sleep(1);

    printf("Master: Now releasing the condition\n");

    pthread_cond_broadcast(&cond);

    for(int i = 0; i < 9; ++i)
        pthread_join(*(ths + i), NULL);

    return 0;
}

And the output is

Thread 2: waiting for release
Thread 6: waiting for release
Thread 4: waiting for release
Thread 1: waiting for release
Thread 3: waiting for release
Thread 8: waiting for release
Thread 9: waiting for release
Thread 7: waiting for release
Thread 5: waiting for release
Master: Now releasing the condition
Thread 5: doing some work for 6 secs, started: 1518463908...
Thread 2: doing some work for 4 secs, started: 1518463908...
Thread 8: doing some work for 1 secs, started: 1518463908...
Thread 4: doing some work for 4 secs, started: 1518463908...
Thread 6: doing some work for 1 secs, started: 1518463908...
Thread 9: doing some work for 5 secs, started: 1518463908...
Thread 3: doing some work for 2 secs, started: 1518463908...
Thread 1: doing some work for 3 secs, started: 1518463908...
Thread 7: doing some work for 2 secs, started: 1518463908...
Thread 8: Bye, end at 1518463909
Thread 6: Bye, end at 1518463909
Thread 3: Bye, end at 1518463910
Thread 7: Bye, end at 1518463910
Thread 1: Bye, end at 1518463911
Thread 2: Bye, end at 1518463912
Thread 4: Bye, end at 1518463912
Thread 9: Bye, end at 1518463913
Thread 5: Bye, end at 1518463914