Многопоточный регистратор вылетает через некоторое время, и я не могу найти, как решить проблему - PullRequest
0 голосов
/ 06 мая 2019

Intro

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

Разбивка проблемы

Способ работы регистратора заключается в добавлении строки в очередь строк.,В потоке LogToConsole существует метод wait, который ожидает, пока в очередь не будет добавлена ​​строка.Когда это происходит, он должен получить уведомление и распечатать, затем выдвинуть и разблокировать.

Используемые переменные

    class Logger
    {
    public:     
        friend void LogToConsole(Logger* logger);

    private:
        std::atomic_flag _ThreadIsRunning { ATOMIC_FLAG_INIT };
        std::thread _LogThread;
        std::queue<std::string> _LogBuffer;
        std::map<std::thread::id, std::string> _ThreadName;
        std::mutex _LogMutex;
        std::mutex _AppendLock;
        std::condition_variable _LogLock;
                ...

Место, куда я добавляю данные в буфер

    template<LogSeverity severity>
    void Logger::Log(std::stringstream& message)
    {
        std::stringstream log;
        log << _ThreadName[std::this_thread::get_id()] << ":\t";
        log << message.str();

        std::unique_lock<std::mutex> logAppendLock(_AppendLock);
        _LogBuffer.push(log.str());
        logAppendLock.unlock();
        _LogLock.notify_one();
    }


    template<LogSeverity severity>
    void Logger::Log(std::string message)
    {
        std::stringstream log;
        log << message.c_str();
        this->Log<severity>(log);
    }

Поток , который выполняется в отдельном цикле (однако обратите внимание, что этот метод не является частью класса регистратора):


    void LogToConsole(Logger* logger)
    {
        do
        {
            std::unique_lock<std::mutex> lock(logger->_LogMutex);
            logger->_LogLock.wait(lock);
            std::printf("%s", logger->_LogBuffer.back().c_str());
            logger->_LogBuffer.pop();
            lock.unlock();
        } while (logger->_ThreadIsRunning.test_and_set() || !logger->_LogBuffer.empty());
    }

Место создания потока

    Logger::Logger() : _LogThread(), _LogBuffer(), _ThreadName(), _LogMutex()
    {
        _ThreadIsRunning.test_and_set();
        _LogThread = std::thread(LogToConsole, this);
    }

Тестовый корпус

        std::shared_ptr<Logger> engineLogger = std::make_shared<Logger>();
        engineLogger->SetThreadName("EngineLogger");

        std::shared_ptr<Logger> coreLogger = std::make_shared<Logger>();
        coreLogger->SetThreadName("CoreLogger");

        while(true)
        {
            engineLogger->Log<LOG_INFO>("LOG\n");
            coreLogger->Log<LOG_WARNING>("WARNING\n");
        }

Код, похоже, работает как потокобезопасный, без данных и т. Д., Но он вылетаетчерез 5-10 ~ секунд.Я искал, если есть люди, у которых есть подобная проблема, это, кажется, не имеет место.

The exception that gets thrown after 10 seconds

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

Надеюсь, что кто-то может решить проблему или дать мне несколько советов, чтобы предотвратить проблему.

1 Ответ

0 голосов
/ 06 мая 2019

В while (logger->_ThreadIsRunning.test_and_set() || !logger->_LogBuffer.empty()); вы получаете доступ к logger без мьютекса, так что это гонка данных. Также _LogBuffer.push(log.str()); доступен только с заблокированным _AppendLock, а logger->_LogBuffer.pop() доступен только с заблокированным _LogMutex, так что это еще одна гонка данных.

Гонки данных - UB и возможная причина сбоя.

...