我正在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);
}
然后在另一个线程中,启动使用者,并检索列表的第一个元素,直到列表为空。
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()
,但我不明白为什么这会成为问题。如果我不断收到数据,我相信整个列表都会被消耗掉。
答案 0 :(得分:6)
否。
您处于正确的位置,但是实际上您不能同时从不同的线程调用任何容器成员函数,并希望它可靠地工作,因为这些成员函数的实现本身[不保证]是线程安全的。
我不明白为什么会出现问题
根据记录,我同意在std::list
的特殊情况下,我不会期望此处遇到实际问题。一些商店可能认为这是充分的理由继续进行生产。我不会在代码审查中接受它。关键是我们根本无法*确切地知道内脏做什么。
话虽这么说,如果您有某些特殊的stdlib实现(或带有特殊标志/开关/选项的stdlib实现)确实提供了这种保证,则将您自己淘汰掉;)*最后,您可以检查它的代码并为自己做出决定,但老实说,疯狂就在于此!
答案 1 :(得分:3)
否
想象一下,列表只有1个元素,而您的2个线程试图“同时”进行推入和弹出操作。
如果您有很多元素,那么从前面弹出一个元素不太可能与后指针交互,并且在后面推一个元素不太可能会干扰前指针,因此/ should /这两个操作是安全的就列表的完整性而言。
但是很明显,当您从最前面弹出最后一个元素时,后面的指针也将得到更新,也就是说没有更多元素,并且当您将一个元素推到后面并且列表为空时,则前指针已更新。您可以保证这些操作的顺序,并且它们不会隐瞒吗?您可以保护大小是否为零,但不能保护大小为1。
与我有关的另一件事是,由于c ++ 11 size()是一个“即时”答案O(0),而不是扫描O(n),因此此列表将保留一个整数。如果push和pop都“同时”修改了尺寸计数器,则它们可能会卡住并最终产生不正确的尺寸值。如果list的内部结构使用size的值作为“ is_empty()”检查,也许是为了优化插入到空列表或弹出最后一个元素,那么它们可能会被愚弄以执行不适当的操作。