std::list 可以用于简单的无锁队列吗?

Could std::list be used for a simple lock-free queue?

本文关键字:队列 简单 list 用于 std      更新时间:2023-10-16

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

No.

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

我不明白为什么这会是一个问题

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

话虽如此,如果你有一些特殊的 stdlib 实现(或带有特殊标志/开关/选项的 stdlib 实现(确实提供了这种保证,那么;)淘汰自己 * 在一天结束时,您可以检查其代码并自己做出决定,但老实说,这就是疯狂!

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

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

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

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