Почему я получаю непостоянный вывод от этого кода? [закрыто]

Вопрос или проблема

Этот код имитирует раздачу супа, предназначенную для равномерного распределения супа среди всех, кто получает его. Он тестируется с 20 людьми, которые должны получить по 1000 тарелок супа каждый. Вывод выглядит правильным примерно в одном из двадцати случаев, но в остальных случаях он раздает весь суп первым 3-4 людям и никому из остальных или просто дает всем случайное количество.

Я считаю, что этот код вызывает конкурентные условия из-за неправильного использования моих мьютексов. Однако все общие переменные защищены, поэтому я не уверен, как это может происходить.

К раздаче супа повторно обращается класс Person, который не защищен мьютексом.

Soupline.cpp:

#include <algorithm>
#include <mutex>
#include <iostream>
#include "../includes/Soupline.h"
#include "../includes/constants.h"
#include "../includes/externs.h"

Soupline::Soupline(int numbBowlsSoup, int numbDrinks) : numbBowlsSoupLeft(numbBowlsSoup), numbDrinksLeft(numbDrinks) {}

int Soupline::getFewestBowlsOfSoupServedToACustomer() {
    if(my_customers.empty()) {
        return ZERO;
    }

    //наименьшее значение в векторе
    auto min_bowl_customer = std::min_element(my_customers.begin(), my_customers.end(),[](const customer& a, const customer& b) {
        return a.numbBowlsSoup < b.numbBowlsSoup;
    });
    std::cout<<"Наименьшее количество тарелок: "<<min_bowl_customer->numbBowlsSoup<<std::endl;
    return min_bowl_customer->numbBowlsSoup;
}

int Soupline::getFewestDrinksServedToACustomer() {
    //std::lock_guard<std::mutex> lock(my_customers_mutex);

    if (my_customers.empty()) {
        return ZERO;
    }

    auto min_drink_customer = std::min_element(my_customers.begin(), my_customers.end(),[](const customer& a, const customer& b) {
        return a.numbDrinks < b.numbDrinks;
    });

    std::cout<<"Наименьшее количество напитков: "<<min_drink_customer->numbDrinks<<std::endl;
    return min_drink_customer->numbDrinks;
}

void Soupline::restock(int numbBowlsSoup, int numbDrinks) {
    std::lock_guard<std::mutex> soupLock(soup_mutex);
    std::lock_guard<std::mutex> drinkLock(drink_mutex);

    numbBowlsSoupLeft = numbBowlsSoup;
    numbDrinksLeft = numbDrinks;
}

int Soupline::getSoup(int personID) {
    std::lock_guard<std::mutex> lock(soup_mutex);

    if(numbBowlsSoupLeft == 0) {
        return OUT_OF_SOUP;
    }

    // проверьте, есть ли ID в векторе людей
    auto current = std::find_if(my_customers.begin(), my_customers.end(), [personID](const customer& customer) {
        return customer.personID == personID;
    });

    if (current == my_customers.end() && numbBowlsSoupLeft > 0) {
        customer new_customer;
        new_customer.personID = personID;
        new_customer.numbBowlsSoup = 1;
        numbBowlsSoupLeft --;
        my_customers.push_back(new_customer);
        std::cout<<"КЛИЕНТ ДОБАВЛЕН"<<std::endl;
        return BOWL_OF_SOUP;
    }

    int leastBowls = getFewestBowlsOfSoupServedToACustomer();

    if (current->numbBowlsSoup > leastBowls) {
        std::cout<<"НЕ ВАШЕ ВРЕМЯ"<<std::endl;
        return NOT_YOUR_TURN;
    }

    current->numbBowlsSoup ++;
    numbBowlsSoupLeft --;

//  std::cout<<"Осталось супа:"<<numbBowlsSoupLeft<<std::endl;

    return BOWL_OF_SOUP;
}

int Soupline::getDrink(int personID) {
    std::lock_guard<std::mutex> lock(drink_mutex);

    if(numbDrinksLeft == 0) {
        return OUT_OF_DRINKS;
    }

    // проверьте, есть ли ID в векторе людей
    auto current = std::find_if(my_customers.begin(), my_customers.end(), [personID](const customer& c) {
        return c.personID == personID;
    });

    if (current == my_customers.end() && numbDrinksLeft > 0) {
        customer new_customer;
        new_customer.personID = personID;
        new_customer.numbDrinks = 1;
        numbDrinksLeft -= 1;
        my_customers.push_back(new_customer);
        return DRINK;
    }

    int leastDrinks = getFewestDrinksServedToACustomer();

    if (current->numbDrinks > leastDrinks) {
        return NOT_YOUR_TURN;
    }

    current->numbDrinks ++;
    numbDrinksLeft --;
    return DRINK;
}

Person.cpp:

#include <iostream>
#include <thread>
#include <vector>
#include <time.h>
#include <chrono> // this_thread::sleep_for(chrono::milliseconds(MILLI_SECONDS_TO_WAIT));
#include "../includes/Person.h"
#include "../includes/constants.h"
#include "../includes/externs.h"
#include "../includes/Soupline.h"

using namespace std;

Person::Person(int personID) : personID(personID), numbBowlsSoupEaten(0), numbDrinksDrunk(0) {}

void Person::eatlunch(){
        bool askForSoup = msl.getnumbBowlsLeft() > 0;
        bool askForDrinks = msl.getnumbDrinksLeft() > 0;

        while(askForSoup || askForDrinks) {
            //std::lock_guard<std::mutex> lock(my_customers_mutex);
            if(askForSoup) {
                //std::cout<<"СПРАШИВАЕТ СУП"<<std::endl;
                //std::lock_guard<std::mutex> soupLock(soup_mutex);
                int soup_return = msl.getSoup(personID);
                //std::cout<<"ВОЗВРАЩАЕТ "<<soup_return<<std::endl;
                if(soup_return == NOT_YOUR_TURN) {
                    std::this_thread::sleep_for(std::chrono::milliseconds(MILLI_SECONDS_TO_WAIT));
                    //std::cout<<"НЕ ВАШЕ ВРЕМЯ ДЛЯ СУПА"<<std::endl;
                }
                else if(soup_return == BOWL_OF_SOUP) {
                    numbBowlsSoupEaten ++;
                    //std::cout<<"СУП СЪЕДЕН клиентом "<<personID<<std::endl;
                }
                else if (soup_return == OUT_OF_SOUP) {
                    askForSoup = false;
                    //std::cout<<"НЕТ СУПА"<<std::endl;
                }
            }

            if(askForDrinks) {
                //std::cout<<"СПРАШИВАЕТ НАПИТКИ"<<std::endl;
                //std::lock_guard<std::mutex> drinkLock(drink_mutex);
                int drink_return = msl.getDrink(personID);
                if(drink_return == NOT_YOUR_TURN) {
                    std::this_thread::sleep_for(std::chrono::milliseconds(MILLI_SECONDS_TO_WAIT));
                    //std::cout<<"НЕ ВАШЕ ВРЕМЯ ДЛЯ НАПИТКА"<<std::endl;
                }
                else if(drink_return == DRINK) {
                    numbDrinksDrunk ++;
                    //std::cout<<"НАПИТКИ УПЕЧЕНЫ"<<std::endl;
                }
                else if (drink_return == OUT_OF_DRINKS) {
                    askForDrinks = false;
                    //std::cout<<"НЕТ НАПИТКОВ"<<std::endl;
                }
            }
        }
}

.

Ответ или решение

Ошибки в многопоточном программировании, особенно в контексте синхронизации доступа к общим ресурсам, часто могут приводить к непредсказуемым результатам, которые сложно диагностировать. Рассмотрим вашу проблему подробнее:

1. Отсутствие синхронизации в критических секциях

Как вы уже заметили, существует вероятность гонок (race conditions) в вашем коде. Вы упомянули, что все переменные, доступные многими потоками, защищены мьютексами, однако в классе Person доступ к переменным происходит без синхронизации. Это может вызвать следующие проблемы:

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

  • Неправильное распределение супа: Ваш метод getSoup проверяет, получили ли другие клиенты суп больше, чем должен, что потребует синхронизации, чтобы избежать того, чтобы один и тот же клиент определялся как следующий в очереди по нескольким потокам.

2. Уровень блокировки

Использование нескольких мьютексов, как soup_mutex и drink_mutex, может приводить к ситуации, когда один поток удерживает один мьютекс, а другой поток пытается получить доступ к другому. Это приводит к потенциальному взаимному блокированию (deadlock) и неэффективности.

Рекомендации:

  • Рассмотрите возможность использования одного мьютекса для всех операций, которые модифицируют состояние, вместо отдельных мьютексов для различных ресурсов. Это упростит управление синхронизацией.

  • Являясь более эффективным, вы можете использовать std::unique_lock для управления блокировкой мьютекса, позволяя вам более гибко управлять жизненным циклом блокировок.

3. Логика доступа

Логика доступа в методах getSoup и getDrink создает ситуации, когда клиент может не получать суп, хотя у него есть право на это. Например, вы сравниваете количество супа у текущего клиента с наименьшим количеством супа, который был выдан:

if (current->numbBowlsSoup > leastBowls) {
    std::cout << "NOT YOUR TURN" << std::endl;
    return NOT_YOUR_TURN;
}

Такое условие может игнорировать ситуации, когда наливается неравномерно множеством клиентов одновременно. Это требует пересмотра логики выдачи и распределения.

4. Рекомендации для улучшения

  • Обеспечьте синхронизацию: Все операции, связанные с изменением состояния клиентов, должны быть обернуты в блоки мьютексов.

  • Проверка состояния: Убедитесь, что перед каждой проверкой состояния переменные синхронизированы, чтобы обеспечить актуальность данных.

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

  • Тестирование: Регулярно проводите тесты с использованием множества потоков, чтобы имитировать реальную ситуацию с клиентами, и наблюдайте за поведением системы на разных стадиях.

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

Оцените материал
Добавить комментарий

Капча загружается...