std::condition_variable wait_until timeout или наступление события

Рейтинг: 1Ответов: 1Опубликовано: 17.01.2023

Не ругайте сильно, это, практически первая попытка написать что-то многопоточное. Сделал некий класс Logger

#include <iostream>
#include <thread>
#include <atomic>
#include <unistd.h>
#include <csignal>
#include <memory>
#include <list>
#include <mutex>
#include <condition_variable>

using std::cout;
using std::endl;
using std::unitbuf;

std::atomic_bool g_stop{false};

class Logger
{
public:
    ~Logger() {};
    static Logger* get_logger()
    {
        static Logger logger;
        return &logger;
    }
    void log(const std::string& _msg)
    {
        std::unique_lock<std::mutex> ul(m_msg_mutex);
        m_messages.push_back(_msg);
        cout << unitbuf << "--- added " << _msg << " size: " << m_messages.size() << endl;
    }
    void log_job(std::atomic_bool& stop)
    {
        while (!stop)
        {
            log("Logger OK");
            std::unique_lock<std::mutex> ul(m_cv_mutex);
            {
                m_cv.wait_for(ul, std::chrono::seconds(m_interval), [&]() { return (m_messages.size() >= m_limit); });
                flush();
            }
        }
        flush();
        cout << unitbuf << "Logger exited" << endl;
    }
protected:
    std::list<std::string> m_messages;
    const unsigned int m_limit {12};
    const unsigned int m_interval {3};
    std::condition_variable m_cv;
    std::mutex m_cv_mutex;
    std::mutex m_msg_mutex;
    Logger() {};
    Logger(const Logger& _obj); // no copies
    const Logger& operator=(const Logger& _obj); // no assignment
    void flush()
    {
        std::unique_lock<std::mutex> ul(m_msg_mutex);
        for (const std::string& s: m_messages) cout << unitbuf << s << endl;
        m_messages.clear();
    }
};


class Device
{
public:
    Device(){}
    void job(std::atomic_bool& stop)
    {
        Logger* logger {Logger::get_logger()};
        int cnt {0};
        while (!stop)
        {
            std::string s = std::string("msg") + std::to_string(cnt++);
            logger->log(s);
            usleep(200000);
        }
    }
};

void sig_handler(int _sig)
{
    switch (_sig)
    {
    case SIGTERM:
        g_stop = true;
        break;
    default:
        cout << "No, no, no, David Blaine, no street magic!" << endl;
    }
}

int main()
{
    signal(SIGTERM, sig_handler);

    Logger* logger = Logger::get_logger();

    std::thread thr([&]() {
        logger->log_job(g_stop);
    });

    Device d;
    std::thread jtht(&Device::job, d, std::ref(g_stop));

    jtht.join();

    thr.join();

    return 0;
}

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

UPD: компилятор - не старше c++14 и нет возможности обновить.

Ответы

▲ 2
  1. У m_cv никогда не вызывается метод notify_one, соответственно проверка условия может произойти только случайно. Его следует вызывать, когда изменяется условие, например в конце log.
  2. Почему-то сделано два мьютекса, m_msg_mutex и m_cv_mutex соответственно при доступе к условию возникает состояние гонки, что является неопределенным поведением.
  3. После вызова wait_for вызывается flush, который пытается рекурсивно захватить мьютекст, что является неопределенным поведением.
  4. Использование в обработчике сигнала функций, не являющихся async-signal-safe (в т.ч. любых методов cout), является неопределенным поведением.