Проблема с потоками мьютекса монитора C++

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

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

#include <iostream>
#include <mutex>
#include <condition_variable>
#include <thread>
#include <chrono>

using namespace std;

class Monitor
{
private:
    string line = "";
    mutex mtx;
    condition_variable cv;
    int vowel_count = 0;
    int max_count = 0;

public:
    void AddA(char symbol)
    {
        unique_lock<mutex> lock(mtx);
        cv.wait(lock, [this] { return vowel_count < 3; });
        line += symbol;
        vowel_count++;
        max_count++;
        cv.notify_all();
    }

    void AddBC(char symbol)
    {
        unique_lock<mutex> lock(mtx);
        cv.wait(lock, [this] { return vowel_count >= 3; });
        line += symbol;
        max_count++;
        vowel_count = 0;
        cv.notify_all();
    }

    string getLine()
    {
        unique_lock<mutex> lock(mtx);
        return line;
    }

    int getVowelCount()
    {
        unique_lock<mutex> lock(mtx);
        return vowel_count;
    }

    int getMaxCount()
    {
        unique_lock<mutex> lock(mtx);
        return max_count;
    }
};

void First(Monitor& monitor)
{
    while (true)
    {
        monitor.AddA('A');

        if (monitor.getMaxCount() == 15)
        {
            break;
        }
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
}

void Second(Monitor& monitor)
{
    while (true)
    {
        monitor.AddBC('B');

        if (monitor.getMaxCount() == 15)
        {
            break;
        }
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
}

void Third(Monitor& monitor)
{
    while (true)
    {
        monitor.AddBC('C');

        if (monitor.getMaxCount() == 15)
        {
            break;
        }
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
}

void Print(Monitor& monitor)
{
    while (true)
    {
        cout << monitor.getLine() << endl;

        if (monitor.getMaxCount() == 15)
        {
            break;
        }

        std::this_thread::sleep_for(std::chrono::milliseconds(1000));
    }
}

int main()
{
    Monitor monitor;

    thread t1(First, ref(monitor));
    thread t2(Second, ref(monitor));
    thread t3(Third, ref(monitor));
    thread t4(Print, ref(monitor));

    t1.join();
    t2.join();
    t3.join();
    t4.join();

    return 0;
}

Я пробовал различные варианты, например, изменял условие на больше или равно 15, но это еще больше усложняет ситуацию.

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

Проблема с Мониторами и Мьютексами в C++

Проблема, о которой идет речь, связана с бесконечным циклом в предоставленном фрагменте кода C++, который использует мьютексы и условные переменные для синхронизации потоков. Основная задача заключается в том, чтобы убедиться, что программа корректно останавливается, когда длина линии достигает 15 символов.

Анализ кода

Класс Monitor существует для управления добавлением символов к строке с учетом определенных условий:

  • Потоки First добавляют символы ‘A’, если количество гласных (в данном случае — только ‘A’) меньше 3.
  • Потоки Second и Third добавляют символы ‘B’ и ‘C’ соответственно, если количество гласных равно или превышает 3.

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

Задача: Улучшение логики синхронизации

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

  1. Ограничить общее количество символов: Каждый поток должен не только проверять условие, относящееся к своему типу символа, но также должна быть проверка на общий max_count.

  2. Использование двух условных переменных: Можно ввести дополнительные условия для проверки, достигнуто ли максимальное количество символов и позволить потокам завершать свою работу корректно.

  3. Использование cv.notify_all(): Это важно для того, чтобы все потоки знали о возможности перезапуска, когда один из потоков завершает свою работу.

Модифицированный код

Вот предложенный модифицированный код, который корректно обрабатывает состояние с максимальным количеством символов:

#include <iostream>
#include <mutex>
#include <condition_variable>
#include <thread>
#include <chrono>

using namespace std;

class Monitor {
private:
    string line = "";
    mutex mtx;
    condition_variable cv;
    int vowel_count = 0;
    int max_count = 0;

public:
    void AddA(char symbol) {
        unique_lock<mutex> lock(mtx);
        cv.wait(lock, [this]() { return vowel_count < 3 && max_count < 15; });
        line += symbol;
        vowel_count++;
        max_count++;
        cv.notify_all();
    }

    void AddBC(char symbol) {
        unique_lock<mutex> lock(mtx);
        cv.wait(lock, [this]() { return vowel_count >= 3 && max_count < 15; });
        line += symbol;
        max_count++;
        vowel_count = 0;
        cv.notify_all();
    }

    string getLine() {
        unique_lock<mutex> lock(mtx);
        return line;
    }

    int getMaxCount() {
        unique_lock<mutex> lock(mtx);
        return max_count;
    }

    bool reachedMaxCount() {
        unique_lock<mutex> lock(mtx);
        return max_count >= 15;
    }
};

void First(Monitor& monitor) {
    while (!monitor.reachedMaxCount()) {
        monitor.AddA('A');
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
}

void Second(Monitor& monitor) {
    while (!monitor.reachedMaxCount()) {
        monitor.AddBC('B');
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
}

void Third(Monitor& monitor) {
    while (!monitor.reachedMaxCount()) {
        monitor.AddBC('C');
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
}

void Print(Monitor& monitor) {
    while (!monitor.reachedMaxCount()) {
        cout << monitor.getLine() << endl;
        std::this_thread::sleep_for(std::chrono::milliseconds(1000));
    }
}

int main() {
    Monitor monitor;

    thread t1(First, ref(monitor));
    thread t2(Second, ref(monitor));
    thread t3(Third, ref(monitor));
    thread t4(Print, ref(monitor));

    t1.join();
    t2.join();
    t3.join();
    t4.join();

    return 0;
}

Заключение

Изменения в синхронизации потоков обеспечивают более надежное завершение работы программы, когда общее количество символов достигает 15. Использование методов cv.wait() и cv.notify_all() в контексте общего состояния потока позволяет избежать бесконечного цикла. Такие улучшения делают вашу программу более эффективной и безопасной.

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

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

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