Можно ли использовать std :: list для простой очереди без блокировки? - PullRequest
0 голосов
/ 12 сентября 2018

Я реализую многопоточное приложение на C ++ 11, которое использует поток для опроса данных из QUdpSocket (каркас QT5) и еще один для обработки собранных данных.

Когда входные данные обнаруживаются сокетом, выдается сигнал ReadyRead(), и все дейтаграммы извлекаются из QUdpSocket и копируются в std::list.

void SocketWrapper::QueuePendingDatagrams()
{    
  while (hasPendingDatagrams()) {
    int dataSize = pendingDatagramSize();
    QByteArray tmpQByte = receiveDatagram().data();
    datagramList.push_back(tmpQByte); // see const_data
  }
  emit newDatagrams(&datagramList);
}

Inдругой поток, потребитель затем запускается и извлекает первый элемент списка до тех пор, пока список не станет пустым.

void Worker::ProcessDatagrams(std::list<QByteArray>* pDatagramList)
{
  while (pDatagramList->size() > 0) {
      QByteArray tmpDatagram = pDatagramList->front();
      pDatagramList->pop_front();

      // Process and log the data within the datagram...
}

Мой вопрос таков: так как я всегда выталкиваю первый элемент и нажимаю после последнего,Является ли этот подход без блокировки поточно-безопасным ?

Я мог бы (и буду) получать больше данных в сокет во время обработки данных, поэтому оба потока будут работать одновременно.Мне кажется, что единственная гонка данных, которая может произойти, может переоценить DatagramList->size(), но я не понимаю, почему это будет проблемой.Если я постоянно получаю данные, я считаю, что весь список будет использован в любом случае.

Ответы [ 2 ]

0 голосов
/ 12 сентября 2018

NO

Представьте, что в списке всего 1 элемент, и ваши 2 потока пытались "одновременно" нажать и вывести.

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

Но ясно, что когда вы вытаскиваете последний элемент спереди, указатель назад также обновляется, говоря, что больше нет элементов, и когда вы нажимаетеэлемент сзади и список пуст, тогда передний указатель обновляется.Можете ли вы гарантировать порядок этих операций, чтобы они не мешали?У вас есть защита на случай, если размер равен нулю, но не на случай, если размер равен 1.

Другая вещь, которая меня беспокоит, заключается в том, что, поскольку c ++ 11 size () является «мгновенным» ответом O (0), а не сканирование O (n), и для этого списка ведется целое число.Если и push, и pop изменяют счетчик размера «одновременно», то они могут получить неправильное значение размера.Если внутренние элементы списка используют значение size в качестве проверки "is_empty ()", возможно, для оптимизации вставки в пустой список или извлечения последнего элемента, то они могут быть одурачены при выполнении неуместных операций.

0 голосов
/ 12 сентября 2018

номер

Вы находитесь на правильных строках, но на самом деле вы не можете вызвать любую функцию-член контейнера из разных потоков одновременно и ожидать, что она будет работать надежно, потому что реализация этих функций-членов сама [гарантированно не будет] потокобезопасным.

Я не понимаю, почему это было бы проблемой

Не для протокола, я бы согласился, что в конкретном случае std::list я бы не ожидал многого в плане практических проблем здесь. Некоторые магазины могут счесть это достаточной причиной, чтобы пойти дальше и использовать это в производстве. Я не принял бы это при проверке кода, хотя. Дело в том, что мы просто не можем * точно знать, что делают внутренности.

При этом, если у вас есть какая-то специальная реализация stdlib (или реализация stdlib со специальным флагом / переключателем / опцией), которая действительно предоставляет эту гарантию, то вырубитесь;) * И в конце дня вы можете изучите его код и примите это решение для себя, но, честно говоря, в этом заключается безумие!

...