线程项目问题 |如何从 C 中的多个线程 wait/signal

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

我实际上在做一个个人项目,我必须制作一个模拟这种行为的小程序:

有两种人:村民和德鲁伊(Asterix/Obelix)

每个 VILLAGER 都由一个 id(村民唯一的编号)标识。它会在离开前战斗“nb_fights”时间 战场。每次战斗前,它必须从罐子里取出一份魔法药水(如果罐子是空的, 它必须通知德鲁伊并等到罐子重新装满)。

DRUID 将等待村民的召唤;然后它将用 pot_size 份重新装满锅。当nb_refills 已完成,德鲁伊 运行 配料用完,它的线程必须停止。

一切都在参数中给出 (./panoramix )

好吧,实际上我完成了项目的 70%,但我无法调用德鲁伊来补充药水。

有村民线程的代码:

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 = 战斗次数(我们减少)

然后是德鲁伊线程:

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");
}

我想知道当“pot_size”为空(=0)时如何“召唤”德鲁伊。

我试着在 if 条件下用信号量来做:

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

(等druid填满pot_size然后继续程序)不过我觉得这样不太好

编辑:完整代码:

#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);
}
  1. 将游戏所需的所有全局变量分组到一个 struct 中,更容易在一个位置访问它们。

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() 不应调用 sem_wait(),因为这是生产者。如果所有村民都打完了,检查他们是否都在睡觉,return/exit 如果他们在睡觉。
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. 村民需要检查德鲁伊是否已经入睡,即。用尽所有 refill-quota。因为,只有一个线程有互斥体并且没有药水可以消耗,我们可以阻塞 sem_wait() 以便德鲁伊重新填充 & 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. 修改后的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. 使用strtol()代替atoi(),它给了你更多的控制权:How to convert a string to integer in C?

  2. 如果线程没有向使用 pthread_join() 等待它们的线程返回任何有意义的内容,您可以使用 pthread_detach()