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 шт):
делать так, как в вопросе - очень плохо. Во первых, во время ожидания могут прийти другие "внезапные пакеты" и что с ними делать? пропускать, обрабатывать?
Также, если к одному сокету на чтение идет несколько потоков, то обычно это заканчивается плохо, даже если хорошо обложиться мютексами. К сокету на чтение должен ходить один поток. С записью тоже бывают проблемы.
Вариант первый - сообщения по запросу и сообщения "случайные" отличаются и мы можем их идентифицировать. Например, таким свойством обладало skype api лет 10 назад. Там к запросу можно было подцепить номерок, и в ответном сообщении придет этот же. Тогда все легко, поток, который хочет отправить сообщение, ставит его в очередь к отправляющему потоку и виснет на мютексе или оставляет кол-бек (тут по вкусу, но с qt слот-сигнал самое оно). Поток, который работает с сокетом, маркирует и отпавляет запрос. Когда придет ответ, сообщение изымается и отправляется адресату. Такая схема реализуется заметно проще.
Вполне может быть, что возможности маркировать пакеты нет, но получив ответ, его можно "угадать". Например, запрашивается состояние какого то устройства и потом приходит пакет с ним. В таком случае номер устройства и может быть идентификатором. Но что, если это не ответ, а "случайный пакет"? а ничего. Тут совсем ничего не сделать, мы же не можем идентифицировать пакеты.
Поэтому появляется следующий вариант решения. Мы не ждем ответа. Мы всегда его готовы получить. А запрос на получение расцениваем как запрос на обновление данных. Если это какая то карточка, которую видит пользователь, то мы ее пользователю показываем в момент отправки и текущие доступные данные (с указанием времени последнего обновления) и колесико "ждите", а прилетевший ответ сразу отображаем. Если же пока окошко открыто, прилетит 2 ответа (на запрос и случайный пакет) - ничего страшного, просто показываем оба. Таким образом работают различные охранные сигнализации. У них запрос состояния датчика это просто запрос. Датчик может и проигнорировать его (например, он просто сломан и физически не может ответить).
Код не привожу. Тут все очень и очень индивидуально.