2

我正在用 C++11 实现一个多线程应用程序,它使用一个线程来轮询来自 QUdpSocket(QT5 框架)的数据,并使用另一个线程来处理收集的数据。

当套接字检测到传入数据时,ReadyRead()会发出一个信号,并将所有数据报从 a 中提取QUdpSocket并复制到 astd::list中。

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

然后在另一个线程中,启动消费者并检索列表的第一个元素,直到列表为空。

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(),但我不明白为什么会出现问题。如果我不断接收数据,我相信无论如何都会消耗整个列表。

4

2 回答 2

6

不。

你在正确的路线上,但实际上你不能同时从不同的线程调用任何容器成员函数并期望它可靠地工作,因为这些成员函数本身的实现不是[保证是]线程-安全的。

我不明白为什么会出现问题

私下里,我同意在这种特殊情况下,std::list我不会对这里的实际问题抱太大希望。一些商店可能认为这是继续并在生产中使用它的充分理由。不过,我不会在代码审查中接受它。关键是我们根本无法*确切地知道内脏在做什么。

话虽这么说,如果您有一些特殊的 stdlib 实现(或带有特殊标志/开关/选项的 stdlib 实现)确实提供了这种保证,那么就把自己淘汰掉;)*最后,您可以检查它的代码并为自己下定决心,但老实说,这就是疯狂所在!

于 2018-09-12T12:20:04.153 回答
3

想象一下列表只有 1 个元素,而您的 2 个线程试图“同时”推送和弹出。

如果你有很多元素,那么从前面弹出一个元素不太可能与后面的指针交互,而在后面推送一个元素不太可能干扰前面的指针,所以这两个操作 /should/ 在以下方面是安全的列表的完整性。

但是很明显,当你从前面弹出最后一个元素时,后面的指针也会更新,也就是说没有更多元素了,当你把一个元素推到后面并且列表为空时,前面的指针是更新。你能保证这些操作的顺序,并且不会卡顿吗?如果大小为零,您有保护,但如果大小为 1,则没有。

我担心的另一件事是,由于 c++11 size() 是“即时”答案 O(0),而不是扫描 O(n),并且要执行此列表,请保持整数计数。如果 push 和 pop “同时”修改大小计数器,那么它们可能会卡住并最终得到不正确的大小值。如果 list 的内部使用 size 的值作为“is_empty()”检查,可能是为了优化插入空列表或弹出最后一个元素,那么他们可能会被欺骗执行不适当的操作。

于 2018-09-12T12:37:30.250 回答