Необходимо ли блокировать объект чтения?

#c #multithreading #locking #mutex #stdlist

Вопрос:

Я пишу программу, у которой есть общая «очередь» строк:

  • 2 или 3 потока возвращаются в очередь
  • Поток пользовательского интерфейса выводит строку из очереди каждые 0,5 — 1 секунду. (Поток пользовательского интерфейса означает CWnd::OnTimer)

Один из потоков записи очень часто отталкивает строку назад. (Строка на самом деле является журналом, который генерируется по 5-10 строк в секунду.)

Я добавляю следующий кодовый снип об объяснении выше.

 class Logger {
    std::mutex m_mutex;
    std::list<std::string> m_queLogs;
public:

    int info(std::string zLog) {
        std::lock_guard<std::mutex> lk(m_mutex);

        m_queLogs.push_back(std::move(zLog));
        ...
    }

    std::string popLog() { //!!!!! Do I need to add a lock_guard here?
        if (m_queLogs.size() == 0)
            return "";

        auto zLog = m_queLogs.front();
        m_queLogs.pop_front();

        return zLog;
    }
}

// Thread 1
int TcpComm::OnRecv(int opCode, char* buf) {

    switch (opCode) {
    case CODE_LOG:                      // generated nearly Real-time 
        Logger::instance()->info(buf);

        break;
        ...
    }
    ...
}

// Thread 2
void MonitorThread(LPVOID lpvoid) {
    while (1) {
        Sleep(60 * 1000);               // generated some times.

        Logger::instance()->info(" ---- monitor signal --- ");
        ...
    }
}


// UI Thread
void CLogView::OnTimer(UINT_PTR nIDEvent)
{
    // every 500 milisecond.
    auto zLog = Logger::instance()->popLog();
    CString strLog = convert_utf8_to_cstring(zLog);

    m_ctrLogView.AppendString(strLog);
}
 

Строки m_queLogs ar никогда не удаляются, только отодвигаются назад в письменных потоках.
Только поток пользовательского интерфейса выводит журнал из m_queLogs .

Я думаю , что нет никаких проблем без блокировки Logger::popLog() , но не уверен, что в случае увеличения частоты регистрации.

Пожалуйста, помогите мне принять решение.

Комментарии:

1. Да, вам нужна блокировка по нескольким причинам.

2. Правило таково: если у вас есть общее состояние, и хотя бы один поток записывает это состояние, вам нужна синхронизация.

3. @NathanOliver Если я добавлю блокировку popLog() , когда частота регистрации увеличивается, представление журнала застревает и мерцает, а также Logger::info застревает, и программа выходит из строя. Вот почему я хочу удалить блокировку popLog() . Неужели нет никакого решения этой проблемы?

Ответ №1:

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

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

Комментарии:

1. Если я проверю std::list::size() > 0 , прежде чем открывать журнал, я думаю, что блокировка не нужна. Что вы думаете, сэр?

2. @codingmonster В этом нет необходимости. Вызов std::list::size должен быть заблокирован, потому что модуль записи будет обновлять его. Кроме того, теперь, когда я его прочитал, ваш читатель на самом деле тоже писатель, потому что он изменяет список.

3. Еще раз извините. В моем случае проверка std::list::size>0 не очень важна. Если он перейдет в пустое состояние, хотя писатель обновит список до size>0 этого момента, читатель в следующий раз откроет журнал. Моя цель-просто избежать сбоев, например, исключения 0xc0000005 , а также ускорить обработку . Существует только один читатель, поэтому часть чтения, о которой вы упомянули, может быть проигнорирована std::list::size>0 . А как насчет этого?