Home > other >  How can I make the pthread tasks executes in the correct order according to their priorities?
How can I make the pthread tasks executes in the correct order according to their priorities?

Time:04-07

I'm trying to create 3 thread task with different priorities. The task will only wait or signal other thread tasks, and save the execution in char array to see that they executed as supposed.

Here is the code

#include <stdio.h>
#include <unistd.h>
#include <stdbool.h>
#include <time.h>
#include <pthread.h>

static void *pT1();
static void *pT2();
static void *pT3();

static void trigT3();
static void trigT2();

pthread_cond_t          gCondVar1;
pthread_cond_t          gCondVar2;
pthread_cond_t          gCondVar3;
pthread_cond_t          gFinish;
pthread_mutex_t         gLock1;
pthread_mutex_t         gLock2;
pthread_mutex_t         gLock3;
pthread_mutex_t         gLockF;

static char             savedExe[4];
static int              gPos = 0;
static bool             gSignalT2 = false;
static bool             gSignalT3 = false;

int main(int argc, char const *argv[])
{

    struct sched_param  param1;
    struct sched_param  param2;
    struct sched_param  param3;

    pthread_attr_t      attr1;
    pthread_attr_t      attr2;
    pthread_attr_t      attr3;

    pthread_t           tid1;
    pthread_t           tid2;
    pthread_t           tid3;

    pthread_attr_init(&attr1);
    pthread_attr_init(&attr2);
    pthread_attr_init(&attr3);

    pthread_attr_setschedpolicy(&attr1, SCHED_FIFO);
    pthread_attr_setschedpolicy(&attr2, SCHED_FIFO);
    pthread_attr_setschedpolicy(&attr3, SCHED_FIFO);

    param1.sched_priority = 10;
    param2.sched_priority = 20;
    param3.sched_priority = 30;

    pthread_attr_setschedparam(&attr1, &param1);
    pthread_attr_setschedparam(&attr2, &param2);
    pthread_attr_setschedparam(&attr3, &param3);

    pthread_create(&tid1, &attr1, pT1, NULL);
    pthread_create(&tid2, &attr2, pT2, NULL);
    pthread_create(&tid3, &attr3, pT3, NULL);

    for (int i = 0; i < sizeof(savedExe);   i) {
        printf("%c, ", savedExe[i]);
    }
    printf("\b\b\n");

    pthread_cond_signal(&gCondVar1);

    /*.
    .
    .
    .*/
    pthread_cond_wait(&gFinish, &gLockF);

    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
    pthread_join(tid3, NULL);

    for (int i = 0; i < sizeof(savedExe);   i) {
        printf("%c, ", savedExe[i]);
    }
    printf("\b\b\n");

    return 0;
}

static void trigT3(){
    pthread_mutex_lock(&gLock3);
    pthread_cond_signal(&gCondVar3);
    gSignalT3 = true;
    pthread_mutex_unlock(&gLock3);
}

static void trigT2(){
    pthread_mutex_lock(&gLock2);
    pthread_cond_signal(&gCondVar2);
    gSignalT2 = true;
    pthread_mutex_unlock(&gLock2);
}

static void *pT1(){
    pthread_mutex_lock(&gLock1);
    pthread_cond_wait(&gCondVar1, &gLock1);
    trigT3();
    gPos  ;
    savedExe[gPos] = '1';
    pthread_mutex_unlock(&gLock1);

    pthread_cond_signal(&gFinish);

    return NULL;
}

static void *pT3(){
    //printf("T3\n");
    pthread_mutex_lock(&gLock3);
    if(!gSignalT3){
        pthread_cond_wait(&gCondVar3, &gLock3);
        gSignalT3 = false;
    }
    trigT2();
    gPos  ;
    savedExe[gPos] = '3';
    pthread_mutex_unlock(&gLock3);
    return NULL;
}

static void *pT2(){
    pthread_mutex_lock(&gLock2);
    if(!gSignalT2){
        pthread_cond_wait(&gCondVar2, &gLock2);
        gSignalT3 = false;
    }
    gPos  ;
    savedExe[gPos] = '2';
    pthread_mutex_unlock(&gLock2);
    return NULL;
}

In the main, I create the 3 different tasks and assign them with different priorities. pT1 has the lowest priority = 10, pT2 = 20 and pT3 = 30. pT1 starts the execution first and since its a lower priority and it triggers pT3, pT3 will start executing. pT3 will trigger pT2 but since pT2 is lower priority, pT3 will finish executing and then it will trigger pT2.

The output of this should be 3, 2, 1. But what I get is 1, 2, 3, as if it does not take care to any priority at all and just executes the tasks in the given order.

CodePudding user response:

Priority affects how much CPU time a thread gets when multiple threads want to use the CPU at once. It's not relevant here.


The problem is that call trig too soon! You want T2 to run after T3, but you trigger T2 before T3 does its thing running. Both end up trying to modify savedExe and gPos at the same time, which is bad. Both problems are avoided by properly ordering the statements.

static void *pT3() {
    wait_for_trigger(&gLock3, &gCondVar3, &gSignalT3);
    savedExe[gPos  ] = '3';
    trigger(&gLock2, &gCondVar2, &gSignalT2);
    return NULL;
}

Demo on Compiler Explorer


Note that you don't need so many mutexes and conv vars. You could use a single mutex and condvar for all signals.

static void wait_for_trigger(bool *flag) {
   pthread_mutex_lock(&mutex);
   while (!*flag)
      pthread_cond_wait(&cond, &mutex);
   pthread_mutex_unlock(&mutex);
}

static void trigger(bool *flag) {
   pthread_mutex_lock(&mutex);
   *flag = true;
   pthread_mutex_unlock(&mutex);
   pthread_cond_broadcast(&cond);
}

Demo on Compiler Explorer

You could even use a single variable for the different priority levels!

static void wait_for_level(int a_prio_lvl) {
   pthread_mutex_lock(&mutex);
   while (prio_lvl > a_prio_lvl)
      pthread_cond_wait(&cond, &mutex);
   pthread_mutex_unlock(&mutex);
}

static void switch_to_level(int a_prio_lvl) {
   pthread_mutex_lock(&mutex);
   prio_lvl = a_prio_lvl;
   pthread_mutex_unlock(&mutex);
   pthread_cond_broadcast(&cond);
}

Demo on Compiler Explorer


You have numerous other problems covered below. These are all fixed in this demo.

Improper prototype

static void *pT1();

should be

static void *pT1(void);

Empty parens in a declaration doesn't mean no arguments. You need to use void to mean that.

Improper arguments

static void *pT1() { ... }

should be

static void *pT1(void *arg) { ... }

pthread_create calls the function with a void * argument.

You can use (void)arg to silence the unused argument warning.

Uninitialized mutexes

pthread_mutex_t gLock1;

should be

pthread_mutex_t gLock1 = PTHREAD_MUTEX_INITIALIZER;

Mutex need to be initialized before they are used!

Uninitialized cond vars

pthread_cond_t gCondVar1;

should be

pthread_cond_t gCondVar1 = PTHREAD_COND_INITIALIZER;

Cond vars need to be initialized before they are used!

Incorrect expectations from pthread_cond_wait

if (!gSignalT3){
   pthread_cond_wait(&gCondVar3, &gLock3);
}

should be

while (!gSignalT3){
   pthread_cond_wait(&gCondVar3, &gLock3);
}

pthread_cond_wait can return at any time. To be clear, pthread_cond_wait can return even if pthread_cond_signal hasn't been used. So it must always be called in a loop that checks some external condition.

Passing unlocked mutex to pthread_cond_wait

pthread_cond_wait(&gFinish, &gLockF);

This suffers from four problems we've already covered:

  • Lack of initializing of gFinish
  • Lack of initialization of gLockF
  • Lack of a required loop
  • Lack of an external condition

But that's not it.

pthread_cond_wait requires a locked mutex, but you are passing an unlocked mutex. It will return immediately with error EINVAL.

Useless code

This is again about

pthread_cond_wait(&gFinish, &gLockF);

It's completely unneeded. pthread_join already waits for the thread to finish. So this serves no purpose.


Full final code:

#include <stdio.h>
#include <stdbool.h>
#include <pthread.h>

static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t  cond  = PTHREAD_COND_INITIALIZER;
static int prio_lvl = 3;

static int  gPos = 0;
static char savedExe[3];

static void wait_for_level(int a_prio_lvl) {
   pthread_mutex_lock(&mutex);
   while (prio_lvl > a_prio_lvl)
      pthread_cond_wait(&cond, &mutex);
   pthread_mutex_unlock(&mutex);
}

static void switch_to_level(int a_prio_lvl) {
   pthread_mutex_lock(&mutex);
   prio_lvl = a_prio_lvl;
   pthread_mutex_unlock(&mutex);
   pthread_cond_broadcast(&cond);
}

static void *pT1(void *arg) {
   (void)arg;
   wait_for_level(1);
   savedExe[gPos  ] = '1';
   return NULL;
}

static void *pT2(void *arg) {
   (void)arg;
   wait_for_level(2);
   savedExe[gPos  ] = '2';
   switch_to_level(1);
   return NULL;
}

static void *pT3(void *arg) {
   (void)arg;
   wait_for_level(3);
   savedExe[gPos  ] = '3';
   switch_to_level(2);
   return NULL;
}

int main(void) {
   pthread_t tid1;
   pthread_t tid2;
   pthread_t tid3;

   pthread_create(&tid1, NULL, pT1, NULL);
   pthread_create(&tid2, NULL, pT2, NULL);
   pthread_create(&tid3, NULL, pT3, NULL);

   pthread_join(tid1, NULL);
   pthread_join(tid2, NULL);
   pthread_join(tid3, NULL);

   {
      const char *format = "%c";
      for (size_t i = 0; i < sizeof(savedExe);   i) {
         printf(format, savedExe[i]);
         format = ", %c";
      }

      printf("\n");
   }

   return 0;
}

CodePudding user response:

Ok, as i understood your requirements. You want to make priority based thread initiation and storing result in what so ever form.

You can achive this by making a simple priority queue of struct having the priority value and the ptherad variable to hold thread information.

only catch is that you need to make a custom priority queue in c over a structure of roughly of this kind

typedef struct prt_qu{
    uint8_t prty;
    pthread th;
} ptr_qu;

Upon execution you just need to enqueue all tasks in priority queue and make the main thread call these threads one by one. You can make main wait for current thread wait by just using pthread_join().

  • Related