Поток не выполняет 2 разные функции 4 раза после ожидания сигнала

#c #multithreading #pthreads #semaphore

#c #многопоточность #pthreads #семафор

Вопрос:

Введение

С помощью этой программы я намерен изучить <semaphore.h> и <pthread.h> в коде C, и это то, что я придумал, чтобы проверить свои текущие базовые знания.

Эта программа моделирует производственную линию для продуктов Q и R:

Q нужны 1A, 1B, 1C, а R нужны 1A, 1C.

Обе функции produce_Q и produce_R используют одни и те же ресурсы

Код

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

sem_t sem_worker[2]; //Semaphores (workers)

//Current stock (initialized as MAX stock)
int stock_A = 5;
int stock_B = 3;
int stock_C = 5;

//Q production method needs 1A, 1B and 1C
void *produce_Q(void *worker)
{
    int w = (int) worker; //Worker id
    
    //Wait if ther are not enough tools 
    while (stock_A < 1 || stock_B < 1 || stock_C < 1) {
        printf("Not enough resources for worker[%d] for Qn", w);
        sem_wait(amp;sem_worker[w]); //Worker set to wait
        sleep(4); //StackOverflow question here
        printf("Resuming worker's [%d] production for Qn", w);
    }

    //If there are tool to produce then
    printf("Worker[%d] takes resources from pool for Qn", w);
    stock_A--;
    stock_B--;
    stock_C--;
    printf("Stock: A=%d, B=%d, C=%d leftn", stock_A, stock_B, stock_C);

    //Random wait to simulate prod
    sleep(rand() % 4);
    printf("Worker[%d] made a Q productn", w);

    //"Return" resources to stockpile
    stock_A  ;
    stock_B  ;
    stock_C  ;
    printf("Worker[%d] returns stock for P productn", w);
    printf("Stock: A=%d, B=%d, C=%d leftn", stock_A, stock_B, stock_C);
}

//R prouction method, needs 1A, 1C
void *produce_R(void *worker)
{
    int w = (int) worker; //Worker id
    
    //Wait if ther are not enough tools 
    while (stock_A < 1 || stock_C < 1) {
        printf("Not enough resources for worker[%d] for Rn", w);
        sem_wait(amp;sem_worker[w]); //Worker set to wait
        sleep(4); //Subject to change for sure
        printf("Resuming worker's [%d] production for Rn", w);
    }

    //If there are tool to produce then
    printf("Worker[%d] takes resources from pool for Rn", w);
    stock_A--;
    stock_B--;
    stock_C--;
    printf("Stock: A=%d, B=%d, C=%d leftn", stock_A, stock_B, stock_C);

    //Random wait to simulate prod
    sleep(rand() % 4);
    printf("Worker[%d] made a R productn", w);

    //"Return" resources to stockpile
    stock_A  ;
    stock_B  ;
    stock_C  ;
    printf("Worker[%d] returns stock for R productn", w);
    printf("Stock: A=%d, B=%d, C=%d leftn", stock_A, stock_B, stock_C);
}

int main(void) {

    int i;
    pthread_t th[4]; //Number of threads to work with

    printf("Setting sems...");
    if (sem_init(amp;sem_worker[0], 0, 1) < 0) error();
    if (sem_init(amp;sem_worker[1], 0, 1) < 0) error();
    if (sem_init(amp;sem_worker[2], 0, 1) < 0) error();
    if (sem_init(amp;sem_worker[3], 0, 1) < 0) error();

    //Create threads
    printf("Create threads...");
    for (i = 0; i < 4; i  ){
        if (pthread_create(amp;th[i], NULL, produce_Q, (void*)i) < 0) error(); //4Q production
        if (pthread_create(amp;th[i], NULL, produce_R, (void*)i) < 0) error(); //4R production
    }

    //Wait for thread death
    printf("Waiting for death...");
    for (i = 0; i < 4; i  ){
        if (pthread_join(th[i], NULL) < 0) error();
    }

    return 0;

}
 

Проблема

Я не получаю ожидаемого результата, который заключается в том, что каждая функция выполняется 4 раза (4Q и 4R), будут моменты, когда ресурсы A, B или C будут равны 0, и один из этих потоков будет ожидать доступности ресурсов.

Я контролирую это здесь:

 while (stock_A < 1 || stock_B < 1 || stock_C < 1) {
        printf("Not enough resources for worker[%d] for Qn", w);
        sem_wait(amp;sem_worker[w]); //Worker set to wait
        sleep(4); //StackOverflow question here
        printf("Resuming worker's [%d] production for Qn", w);
    }
 

но этого недостаточно… Я реализовал этот «sleep (4)» как «исправляющий патч, который работает», но это не решает мою главную проблему:

«Есть рабочие, которые ждут ресурсов, но не производят вторичные продукты, я что-то упускаю в этом фрагменте кода ожидания?»

Комментарии:

1. sleep() это функция синхронизации , а не функция синхронизации. Вы могли бы правдоподобно использовать его для моделирования времени, фактически затраченного производственным оборудованием, которое вы имитируете, но оно не играет соответствующей роли в синхронизации их действий.

2. Вы объявляете sem_worker как массив с двумя элементами, но затем продолжаете использовать его, как если бы у него было четыре. Результаты неопределенного поведения.

3. Неопределенное поведение для несинхронизированного, не только для чтения, неатомного доступа к объекту из нескольких потоков.

Ответ №1:

В вашем коде множество проблем. Некоторые из более крупных:

  • вы запускаете 8 потоков, но присоединяетесь только к четырем (см. Также Ниже). Вы не можете присоединиться к остальным четырем, потому что вы перезаписали их идентификаторы потоков:
          for (i = 0; i < 4; i  ){
             if (pthread_create(amp;th[i], NULL, produce_Q, (void*)i) < 0) error(); //4Q production
             if (pthread_create(amp;th[i], NULL, produce_R, (void*)i) < 0) error(); //4R production
         }
     

    Обратите внимание, в частности, что на каждой итерации вы сохраняете идентификатор потока для каждого нового потока в одном и том же элементе массива. Если вы также не присоединитесь к остальным четырем, то вы не можете быть уверены, что они завершат свою работу до принудительного завершения при завершении всей программы.

  • Вы используете несколько независимых семафоров для защиты доступа к одним и тем же общим переменным. Это неверно и неэффективно. Несколько ресурсов могут быть защищены одним и тем же семафором или мьютексом, но суть в том, чтобы предотвратить одновременный доступ нескольких потоков к общим переменным, поэтому, если потокам требуется заблокировать только один из нескольких семафоров для получения доступа, эффективной защиты не существует.
  • Вы должны синхронизировать все обращения к общим переменным. Это включает в себя проверку их значений в состоянии ваших while циклов.
  • Как только поток завершает доступ к общим переменным, ему необходимо разблокировать семафор с sem_post() помощью, чтобы позволить другим потокам получить его. Ваши потоки никогда этого не делают. В этом моделировании имело бы смысл, чтобы потоки разблокировали семафор после получения ресурсов, но перед «производством» своего продукта. В этом случае они захотят снова заблокировать его перед возвратом ресурсов и снова освободить его после этого.
  • Как я писал в комментариях, sleep() это не функция синхронизации. У него нет действительной роли для синхронизации. Однако вы можете обнаружить, что без этих вызовов у вашей программы возникают проблемы с прогрессом. Это связано с тем, что программы, которым требуется приостановить выполнение потока в ожидании выполнения условия, должны, как правило, строиться вокруг переменных условий. Это позволяет потокам приостанавливаться до получения уведомления о том, что стоит проверить условие, вместо того, чтобы получать необходимую блокировку и проверять условие при каждой возможности.
    • Однако подходящим объектом блокировки для использования с переменными условий pthreads является мьютекс (pthreads), а не семафор. Если вы хотите использовать семафоры, вам нужна другая парадигма.

Кроме того, ваша программа имеет несколько странную структуру. Он не только пытается использовать семафоры, где мьютекс условная переменная была бы более естественной, но и более типичный подход к такой задаче моделирования производства будет включать только один поток для каждой производственной линии, каждый из которых выполняет функцию, которая выполняет цикл для создания нескольких элементов. Такая циклическая структура была бы более приемлемой для использования семафоров. Это будет работать следующим образом:

  1. Для каждого потока существует отдельный семафор. Только одна инициализируется значением 1; другие (остальные) инициализируются значением 0.
  2. Потоки принудительно выполняются в строгой циклической последовательности для получения блокировок. Для этого, когда каждый поток выходит из критической области, он разблокирует семафор следующего потока.
  3. Когда потоки завершают свою обычную работу, они должны продолжать участвовать в циклической блокировке семафора, пока все остальные потоки тоже не завершат свою работу.
  4. Следовательно, должен быть еще один или несколько фрагментов общих данных, по которым потоки могут определить, когда вся работа выполнена.

Комментарии:

1. Большое вам спасибо, я ценю все усилия и подробный ответ. Это мне очень помогает.