std::condition_variable гонка данных

Всем привет, ситуация такая:

Есть клиент, у которого в потоке в бесконечном цикле читаются из сокета данные (прикладывают карточку, её данные могут придти внезапно), также у клиента есть методы отправки и чтения данных в сокете Send и Read. Так вот когда вызывается метод отправки данных, поток с бесконечным чтением приостанавливается, потому что 100% должны придти данные в ответ на те, которые были явно отправлены через метод Send.

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

Методы, которые работают в потоке (50 при передаче в сокет - таймаут в мс) :

void Client::ReadCardID() {
    while (_isCardThreadWork) {
        unique_lock<mutex> locker(_mutex);
        _cardCV.wait(locker, [=]() { return !_isCardThreadPaused; });
        locker.unlock();

        if (!_isCardThreadWork)
            return;

        if (_socket->CheckForReadyRead(50)) {
            ProcessCardData();
        }
    }
}

void Client::ProcessCardData() {
    const auto dataPacket = Read(false);

    if (dataPacket && (dataPacket->first == 0x01 || dataPacket->first == 0x02)) {
        vector<uint8_t> tempCard;
        tempCard.push_back(dataPacket->first);
        tempCard.insert(tempCard.end(), dataPacket->second.begin(), dataPacket->second.end());

        if (!_isIgnoreEcho || (_isIgnoreEcho && *_cardData != tempCard)) {
            _cardData = move(tempCard);
        }
    }
}

Метод отправки данных:

optional<vector<uint8_t>> Client::Send(CommandTypes type) {
    if (_socket) {
        SuspendCardThread();
        auto command = static_cast<uint8_t>(type);
        auto data = Commands::GenerateData();
        auto packetWriteBuffer = Wiegand::GeneratePacket(command, data);

        ssize_t n = _socket->Write(packetWriteBuffer.data(), packetWriteBuffer.size());

        if (n < 0) {
            ResumeCardThread();
            return nullopt;
        }

        return packetWriteBuffer;
    }

    return nullopt;
}

Метод чтения данных:

optional<pair<uint8_t, array<uint8_t, 10>>> Client::Read(bool checkForHang, uint16_t size) {
    if (_socket) {
        vector<uint8_t> packetReadBuffer(size);
        _socket->Read(packetReadBuffer.data(), packetReadBuffer.size(), checkForHang);

        if (checkForHang)
            ResumeCardThread();

        try {
            auto outputData = Wiegand::ParsePacket(packetReadBuffer);
            return outputData;
        }
        catch (const runtime_error& msg) {
            return nullopt;
        }
    }

    return nullopt;
}

Методы остановки и пробуждения потока:

void Client::SuspendCardThread() {
    unique_lock<mutex> locker(_mutex);
    qDebug() << "SUSPENDED";
    _isCardThreadPaused = true;
}

void Client::ResumeCardThread() {
    unique_lock<mutex> locker(_mutex);
    _isCardThreadPaused = false;
    qDebug() << "RESUMED";
    _cardCV.notify_one();
}

Ответы (1 шт):

Автор решения: KoVadim

делать так, как в вопросе - очень плохо. Во первых, во время ожидания могут прийти другие "внезапные пакеты" и что с ними делать? пропускать, обрабатывать?

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

Вариант первый - сообщения по запросу и сообщения "случайные" отличаются и мы можем их идентифицировать. Например, таким свойством обладало skype api лет 10 назад. Там к запросу можно было подцепить номерок, и в ответном сообщении придет этот же. Тогда все легко, поток, который хочет отправить сообщение, ставит его в очередь к отправляющему потоку и виснет на мютексе или оставляет кол-бек (тут по вкусу, но с qt слот-сигнал самое оно). Поток, который работает с сокетом, маркирует и отпавляет запрос. Когда придет ответ, сообщение изымается и отправляется адресату. Такая схема реализуется заметно проще.

Вполне может быть, что возможности маркировать пакеты нет, но получив ответ, его можно "угадать". Например, запрашивается состояние какого то устройства и потом приходит пакет с ним. В таком случае номер устройства и может быть идентификатором. Но что, если это не ответ, а "случайный пакет"? а ничего. Тут совсем ничего не сделать, мы же не можем идентифицировать пакеты.

Поэтому появляется следующий вариант решения. Мы не ждем ответа. Мы всегда его готовы получить. А запрос на получение расцениваем как запрос на обновление данных. Если это какая то карточка, которую видит пользователь, то мы ее пользователю показываем в момент отправки и текущие доступные данные (с указанием времени последнего обновления) и колесико "ждите", а прилетевший ответ сразу отображаем. Если же пока окошко открыто, прилетит 2 ответа (на запрос и случайный пакет) - ничего страшного, просто показываем оба. Таким образом работают различные охранные сигнализации. У них запрос состояния датчика это просто запрос. Датчик может и проигнорировать его (например, он просто сломан и физически не может ответить).

Код не привожу. Тут все очень и очень индивидуально.

→ Ссылка