Home > Net >  Thread project problem | How to wait/signal from multiple thread in C
Thread project problem | How to wait/signal from multiple thread in C

Time:04-25

I'm actually doing a personal project where I have to make a little program that simulate this behavior :

There is 2 type of people : Villager and Druid (as Asterix/Obelix)

Each VILLAGER is identified by an id (a number unique to the villager). It will fight "nb_fights" time before leaving the battlefield. Before each fight, it must take a serving of magical potion from the pot (if the pot is empty, it must inform the druid and wait until the pot is refilled).

The DRUID will wait to be called by a villager; then it will refill the pot with pot_size servings. When nb_refills have been done, the druid has run out of ingredients and it’s thread must stop.

Everything is given in the parameters (./panoramix <nb_villagers> <pot_size> <nb_fights> <nb_refills>)

Well, actually I did 70% of the project but I have trouble calling the druid to refill the potions.

There is the code for the Villager thread :

void *villager(void *args)
{
    int pNb = nbF;
    int id = *(int *)args;
    
    printf("Villager %d: Going into battle!\n", id);
    while (pNb > 0) {
        pthread_mutex_lock(&mpot);
        printf("Villager %d: I need a drink... I see %d servings left.\n", id, pot_size);
        if (pot_size == 0) {
            printf("Villager %d: Hey Pano wake up! We need more potion.\n", id);
            sem_wait(&test);
        }
        pot_size--;
        pthread_mutex_unlock(&mpot);
        pNb--;
        printf("Villager %d: Take that roman scum! Only %d left.\n", id, pNb);
    }
    printf("Villager %d: I’m going to sleep now.\n", id);
}

WITH pNb = Number of fight (that we decrement)

then there is the Druid thread :

void *druid(void *args)
{
    int sizepot = pot_size;

    printf("Druid: I'm ready... but sleepy...\n");
    while (nb_refills > 0) {
        while (pot_size != 0) {
        }
        sem_wait(&test);
        nb_refills--;
        printf("Druid: Ah! Yes, yes, I'm awake! Working on it! Beware I can only make %d more refills after this one.\n", nb_refills);
        pot_size = sizepot;
        sem_post(&test);
    }
    printf("Druid: I'm out of viscum. I'm going back to... zZz\n");
}

I would like to know how I can "call" the druid when the "pot_size" is empty (=0).

I tried to do it with semaphore in the if condition :

        if (pot_size == 0) {
            printf("Villager %d: Hey Pano wake up! We need more potion.\n", id);
            sem_wait(&test);
        }

(wait the druid for filling the pot_size and then continue the program) but I think this is not really good.

EDIT : THE ENTIRE CODE :

#include "struct.h"

sem_t test;
pthread_mutex_t mpot;
pthread_mutex_t fill;
int nbV = 0;
int pot_size = 0;
int nbF = 0;
int nb_refills = 0;

int errorhandler(int ac, char **av)
{
    if (ac != 5)
    {
        printf("USAGE: ./panoramix <nb_villagers> <pot_size> <nb_fights> <nb_refills> \nValues must be > 0.\n");
        return (84);
    }
    if (atoi(av[1]) == 0 || atoi(av[2]) == 0 || atoi(av[3]) == 0 || atoi(av[4]) == 0)
    {
        printf("USAGE: ./panoramix <nb_villagers> <pot_size> <nb_fights> <nb_refills> \nValues must be > 0.\n");
        return (84);
    }
    return (0);
}

void *druid(void *args)
{
    int sizepot = pot_size;

    printf("Druid: I'm ready... but sleepy...\n");
    while (nb_refills > 0) {
        while (pot_size != 0) {
            usleep(1000);
        }
        pthread_mutex_lock(&fill);
        nb_refills--;
        printf("Druid: Ah! Yes, yes, I'm awake! Working on it! Beware I can only make %d more refills after this one.\n", nb_refills);
        pot_size = sizepot;
        pthread_mutex_unlock(&fill);
    }
    printf("Druid: I'm out of viscum. I'm going back to... zZz\n");
}

void *villager(void *args)
{
    int pNb = nbF;
    int id = *(int *)args;
    
    printf("Villager %d: Going into battle!\n", id);
    while (pNb > 0) {
        pthread_mutex_lock(&mpot);
        printf("Villager %d: I need a drink... I see %d servings left.\n", id, pot_size);
        if (pot_size == 0) {
            printf("Villager %d: Hey Pano wake up! We need more potion.\n", id);
        }
        pot_size--;
        pthread_mutex_unlock(&mpot);
        pNb--;
        printf("Villager %d: Take that roman scum! Only %d left.\n", id, pNb);
    }
    printf("Villager %d: I’m going to sleep now.\n", id);
}

int main(int ac, char **av)
{
    if (errorhandler(ac, av) == 84)
        return (84);
    nbV = atoi(av[1]);
    pot_size = atoi(av[2]);
    nbF = atoi(av[3]);
    nb_refills = atoi(av[4]);
    pthread_t th[nbV];
    int i;

    pthread_mutex_init(&mpot, NULL);
    pthread_mutex_init(&fill, NULL);
    sem_init(&test, 0, 1);
    for (int i = 0; i < nbV; i  ) {
        int *a = malloc(sizeof(int));
        *a = i;
        if (pthread_create(&th[i], NULL, &villager, a) != 0) {
            perror("Failed to create the Villager\n");
        }
    }
    pthread_create(&th[nbV], NULL, &druid, NULL);
    for (int i = 0; i < atoi(av[1]); i  ) {
        if (pthread_join(th[i], NULL) != 0) {
            perror("Failed to launch the Villager\n");
        }
    }
    sem_destroy(&test);
    pthread_mutex_destroy(&mpot);
    pthread_mutex_destroy(&fill);
    return (0);
}

CodePudding user response:

  1. Group all global variables you need for the game into a struct, becomes easier to access them at a location.

typedef struct {
    int nbV;            // # of villagers
    int nbF;            // # of fights each villager takes on
    int nb_refills;     // # of potion refills that druid can make
    int pot_size;       // size of potion pot
    int potion_left;    // remaining potion in pot for consumption
    int druid_awake;    // flag only modified by druid
    int villagers_awake;
    sem_t pot_sem;
    pthread_mutex_t potion_mutex;
} sPanoramix_t;

sPanoramix_t pgame;
  1. druid() should not call sem_wait(), as this is the producer. If all the villagers are done with fighting, check if all of them sleeping, return/exit if they're.
void *druid (void *args) {
    printf ("Druid: I'm ready... but sleepy...\n");
    pgame.druid_awake = 1;
    while (pgame.nb_refills > 0) {
        while (pgame.potion_left > 0) {
            if (!pgame.villagers_awake) {
                printf ("Druid: KILLED all of them today! Drinks are on me :-)\n");
                return NULL;
            }
            usleep (1000);
        }
        pgame.nb_refills--;
        printf ("Druid: Ah! Yes, yes, I'm awake! Working on it! Beware I can only make %d more refills after this one.\n", pgame.nb_refills);
        pgame.potion_left = pgame.pot_size;
        printf ("Druid: Kill those romans, potion refilled to %d\n", pgame.potion_left);
        sem_post (&pgame.pot_sem);
    }
    printf ("Druid: ALERT!! I'm out of viscum. I'm going back to... zZz\n");
    pgame.druid_awake = 0;    // druid is not available anymore
    return NULL;
}
  1. Villagers need to check if druid has gone to sleep, ie. exhausted all refill-quota. Since, only one thread has the mutex & no potion to consume, we can block on sem_wait() for druid to refill & sem_post().
void *villager (void *args) {
    int pNb = pgame.nbF;
    int id = (unsigned long)args;

    printf ("Villager %d: Going into battle!\n", id);
    while (pNb > 0) {
        pthread_mutex_lock (&pgame.potion_mutex);
        printf ("Villager %d: I need a drink... I see %d servings left.\n", id, pgame.potion_left);
        if (pgame.potion_left <= 0) {
            if (!pgame.druid_awake) {
                printf ("Villager %d: We're doomed now Pano!\n", id);
                pthread_mutex_unlock (&pgame.potion_mutex);
                return NULL;
            }
            printf ("Villager %d: Hey Pano wake up! We need more potion.\n", id);
            sem_wait (&pgame.pot_sem);
            printf ("Villager %d: Thank you PANO!\n", id);
        }
        pgame.potion_left--;
        pthread_mutex_unlock (&pgame.potion_mutex);
        pNb--;
        printf ("Villager %d: Take that Roman scum! Only %d left.\n", id, pNb);
        usleep (1000); // let other villagers take turn
    }
    printf ("Villager %d: KILLED my quota of Romans, I’m going to sleep now.\n", id);
    pthread_mutex_lock (&pgame.potion_mutex);
    pgame.villagers_awake --;
    pthread_mutex_unlock (&pgame.potion_mutex);

    return NULL;
}
  1. Modified main() :
int main (int ac, char **av) {
    if (errorhandler (ac, av) == 84)
        return (84);

    pgame.nbV = atoi (av[1]);
    pgame.villagers_awake = pgame.nbV;
    pgame.pot_size = atoi (av[2]);
    pgame.potion_left = pgame.pot_size;
    pgame.nbF = atoi (av[3]);
    pgame.nb_refills = atoi (av[4]);
    pthread_t th[pgame.nbV   1];

    pthread_mutex_init (&pgame.potion_mutex, NULL);
    sem_init (&pgame.pot_sem, 0, 1);
    pthread_create (&th[0], NULL, druid, NULL);
    usleep (1000);
    for (int i = 1; i <= pgame.nbV; i  ) {
        if (pthread_create (&th[i], NULL, villager, (void*) (unsigned long)i) != 0)
            perror ("Failed to create the Villager\n");
    }
    for (int i = 0; i <= pgame.nbV; i  ) {
        if (pthread_join (th[i], NULL) != 0)
            perror ("Failed pthread_join()\n");
    }
    sem_destroy (&pgame.pot_sem);
    pthread_mutex_destroy (&pgame.potion_mutex);

    printf ("End of game Panoramix!\n");
    return (0);
}

  1. Use strtol() instead of atoi(), it gives you more control: How to convert a string to integer in C?

  2. You can use pthread_detach() if threads are not returning anything meaningful to a thread which waits for them with pthread_join().

  • Related