std::list 可以用于简单的无锁队列吗?
Could std::list be used for a simple lock-free queue?
我正在用 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()
,但我不明白为什么这会成为问题。如果我不断地接收数据,我相信无论如何都会消耗整个列表。
没有
你是对的,但实际上你不能同时从不同的线程调用 any 容器成员函数并期望它可靠地工作,因为这些成员函数本身的实现不是[保证]线程安全的。
I don't see why it would be a problem
不公开,我同意在 std::list
的特殊情况下,我不会对此处的实际问题抱太大期望。一些商店可能认为这是继续并在生产中使用它的充分理由。不过,我不会在代码审查时接受它。关键是我们根本无法*确切地知道内部结构是做什么的。
话虽这么说,如果您有一些特殊的 stdlib 实现(或带有特殊 flag/switch/option 的 stdlib 实现)确实提供了这种保证,那么请把自己搞砸 ;) * 最后您可以检查其代码并自己做出决定,但老实说,这是疯狂的谎言!
没有
假设列表只有 1 个元素,而您的 2 个线程试图 "simultaneously" 推送和弹出。
如果你有很多元素,那么从前面弹出一个元素不太可能与后面指针交互,而将一个元素推到后面不太可能干扰前面指针,所以这两个操作/应该/就列表的完整性而言是安全的。
但是很明显,当你从前面弹出最后一个元素时,后面的指针也会更新,也就是说没有更多的元素,当你将一个元素推到后面并且列表为空时,然后更新前指针。你能保证这些操作的顺序,并且它们不会乱七八糟吗?如果大小为零,您有保护,但如果大小为 1,则没有保护。
另一件让我担心的事情是,由于 c++11 size() 是一个 "instant" 答案 O(0),而不是扫描 O(n),并且要执行此列表保持整数计数.如果 push 和 pop 都修改大小计数器 "simultaneously",那么它们可能会混乱并最终得到不正确的大小值。如果 list 的内部使用 size 的值作为 "is_empty()" 检查,也许是为了优化插入到空列表或弹出最后一个元素,那么它们可能会被愚弄以执行不适当的操作。
我正在用 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()
,但我不明白为什么这会成为问题。如果我不断地接收数据,我相信无论如何都会消耗整个列表。
没有
你是对的,但实际上你不能同时从不同的线程调用 any 容器成员函数并期望它可靠地工作,因为这些成员函数本身的实现不是[保证]线程安全的。
I don't see why it would be a problem
不公开,我同意在 std::list
的特殊情况下,我不会对此处的实际问题抱太大期望。一些商店可能认为这是继续并在生产中使用它的充分理由。不过,我不会在代码审查时接受它。关键是我们根本无法*确切地知道内部结构是做什么的。
话虽这么说,如果您有一些特殊的 stdlib 实现(或带有特殊 flag/switch/option 的 stdlib 实现)确实提供了这种保证,那么请把自己搞砸 ;) * 最后您可以检查其代码并自己做出决定,但老实说,这是疯狂的谎言!
没有
假设列表只有 1 个元素,而您的 2 个线程试图 "simultaneously" 推送和弹出。
如果你有很多元素,那么从前面弹出一个元素不太可能与后面指针交互,而将一个元素推到后面不太可能干扰前面指针,所以这两个操作/应该/就列表的完整性而言是安全的。
但是很明显,当你从前面弹出最后一个元素时,后面的指针也会更新,也就是说没有更多的元素,当你将一个元素推到后面并且列表为空时,然后更新前指针。你能保证这些操作的顺序,并且它们不会乱七八糟吗?如果大小为零,您有保护,但如果大小为 1,则没有保护。
另一件让我担心的事情是,由于 c++11 size() 是一个 "instant" 答案 O(0),而不是扫描 O(n),并且要执行此列表保持整数计数.如果 push 和 pop 都修改大小计数器 "simultaneously",那么它们可能会混乱并最终得到不正确的大小值。如果 list 的内部使用 size 的值作为 "is_empty()" 检查,也许是为了优化插入到空列表或弹出最后一个元素,那么它们可能会被愚弄以执行不适当的操作。