Пример тупиковой ситуации по условной переменной

У меня возникла тупиковая ситуация, которую я не могу объяснить с помощью приведенного ниже кода. Я ожидал, что код должен работать из-за ложного пробуждения, но думаю, что что-то упускаю. Я проверил это Взаимная блокировка переменной условия, но безуспешно.

class SafeQueue
{
private:
    std::queue<int> data_queue;
    mutable std::mutex m;
    std::condition_variable cv;
    std::atomic<bool> flag{ true }; // Atomic flag to control the queue

public:
    void push(int val)
    {
        std::lock_guard<std::mutex> lock(m);
        data_queue.push(val);
        cv.notify_one(); // Notify waiting thread upon pushing data
    }

    bool pop(int& val)
    {
        std::unique_lock<std::mutex> lock(m);
        cv.wait(lock, [this]() { return !data_queue.empty() || !flag; }); // Wait until queue is not empty or flag is turned off

        if (!flag && data_queue.empty())
        {
            return false; // Queue is empty and flag is off, return false to indicate termination
        }

        if (!data_queue.empty())
        {
            val = data_queue.front();
            data_queue.pop();
            return true;
        }
        return false;
    }

    void turnOff()
    {
        flag = false;
    }

    bool isFlagOn() const
    {
        return flag;
    }
};

void consumerLoop(SafeQueue& q)
{
    while (q.isFlagOn())
    {
        int val;
        if (q.pop(val))
        {
            std::cout << "Consumed: " << val << std::endl;
        }
    }
    std::cout << "Consumer exiting" << std::endl;
}

int main()
{
    SafeQueue q;
    std::thread consumerThread(consumerLoop, std::ref(q));

    // Producer pushes some values into the queue
    for (int i = 0; i < 10; ++i)
    {
        q.push(i);
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }

    // Turn off the queue flag
    q.turnOff();

    consumerThread.join();

    return 0;
}

Можете ли вы помочь указать, где это происходит и как исправить реализацию? Любые другие предложения по реализации приветствуются (мне нужен дек, я просто включил пример для очереди для простоты)

🤔 А знаете ли вы, что...
Язык C++ активно развивается, и новые стандарты регулярно добавляют новые возможности.


2
65
1

Ответ:

Решено

Ваш consumerThread висит на cv.wait и просыпается только тогда, когда push делает cv.notify_one(). Это происходит 10 раз, затем цикл продюсера завершается и flag отключается, однако consumerThread все еще/снова висит на cv.wait.

На этом этапе ваш consumerThread застрянет до тех пор, пока:

  • Кто-то звонит cv.notify_one() (или cv.notify_all())
  • Вы получаете ложное пробуждение, но помните, что в основном это дефекты, которые вы можете получить в различных реальных условиях (вот почему вам нужно писать код так, как будто они могут произойти), но они не должны происходить.
  • Вы завершаете программу

Довольно простое решение в этом случае — сделать следующее:

void turnOff()
{
    flag = false;
    cv.notify_one();
}