Qt 4.7:TCP线程,数据传输导致内存泄漏
Posted
技术标签:
【中文标题】Qt 4.7:TCP线程,数据传输导致内存泄漏【英文标题】:Qt 4.7: TCP thread, data transfer causes memory leak 【发布时间】:2014-10-31 22:35:44 【问题描述】:我自己解决了这个问题,赏金不会发放。该问题是由非 GUI 线程启动的 GUI 操作引起的。
Qt 4.7 OSX 10.6.8
应用中有很多代码,但并没有涉及到正在发生的事情。
数据内存泄漏发生在单个连接的上下文中,在单个 Qt 线程中打开、读取、写入和关闭。我使用固定内存对象 (pMsg) 来保存我的消息,然后将它们发送到外部设备,如下所示:
m_pTcpSocket->write((char*)pMsg->Buf8, (qint64)pMsg->GetLength());
Buf8 是一个 2048 字节的静态数组。 GetLength 是消息的前 16 位,并针对 0xFF,因此是一个从 0 到 255 的数字。对于这些消息,应该返回 4,在我的诊断中总是如此。这两个操作都被它们自己的互斥体(意思是不同的互斥体)包围。消息长度通常为 4 个字节。消息可靠地到达我们有线 LAN 上其他地方的接收设备;当它们到达时它们是正确的,并且设备会使用仅针对这些消息的 ACK 进行适当的响应。之后我尝试添加对 flush() 的调用;没有帮助(也不应该有任何东西要刷新,但是......)我不知道泄漏是在 write() 中。
依次发送这些消息会导致我收到来自设备的 ACK 消息。我是这样读的:
if (m_pTcpSocket->waitForReadyRead(100))
while ((bytesavailable = m_pTcpSocket->bytesAvailable()))
m_pTcpSocket->read(RBuf, bytesavailable);
AssembleMsg(Buf, bytesavailable); // state machine empties Buf
在循环之后,bytesavailable 为零(当然。)Buf 是一个指向 2048 个无符号字符静态数组的无符号字符指针,在接收到每个数据部分后,我运行一个简单的状态机来组装消息。消息长度为 4。消息按预期接收和组装,没有进行内存分配,也没有声明对象。这两个操作都被它们自己的互斥体包围(意思是,不同的互斥体,因此它们不能在 rx 和 tx 之间进行交互。)一旦消息被组装,它所做的就是重置一个计数器,该计数器将延迟设置为下一个 keepalive 消息(即这些是什么。没有它们,设备将断开连接。)通过在 waitforreadyread(100) 之后计数来累积延迟,只要设备不向该端口发送任何内容,它就会计算该长度的间隔,这是典型行为。这样,就不需要定时器了。时机很好。消息一到就被读取,或者至少在 100 毫秒内被读取。他们不积累。所以我认为读取缓冲区不会变大(er)。但是……我不知道。东西变大了!
这就是阅读。但我也不知道泄漏在 read() 中。
但它必须是其中之一。如果我不发送这些消息(这意味着我也没有收到 ACK 消息),那么就没有泄漏。应用程序的任何地方都没有其他任何变化。这是它启动的模式,没有其他活动正在进行,我只是保持连接打开,所以当需要运行无线电时,端口已准备就绪。
它们都在同一个线程中运行,并且它们都运行在同一个套接字上。线程连续运行,并且同一个套接字保持打开状态(实际上是几个小时。)所以这不是一个套接字对象删除问题。
某些品牌的 SDR 收音机会加剧该问题,因为它们在接收操作期间需要保持连接,这意味着应用程序在接收时会像疯了一样坐在那儿并像在等待离开时一样好地咀嚼内存。
我在大约 12 小时内丢失了大约 250 兆字节,在 100k 以下的块中。我可以看到应用程序内存每次增加 1 mb,大约每秒一次。
我在 Google 上进行了广泛搜索,我能找到的所有内容都是无法通过多个连接删除 tcp 对象,这绝对不是这里的问题。
我真的很茫然。问题与我在线程中使用套接字有关吗?该应用程序(一个非常复杂的软件定义无线电应用程序)运行 10 到 16 个线程,具体取决于它在做什么;我在它们自己的线程中运行数据传输,因此它们不会受到任何与主事件循环相关的影响。
我已经尝试过 valgrind,但它会在尝试启动应用程序之后终止应用程序,远在所有这些开始之前。我不认为它喜欢线程或其他东西。或者可能是 10.6.8,但无论如何,它不起作用。 Qt 4.7 无论如何都没有集成它。我知道无法从应用程序中跟踪内存使用情况,以便我可以包装每个发送和接收,并至少找出哪个(或两者都负责?)负责。
*** 编辑:通过改变keepalive消息的速率,我直接改变了内存泄漏的速率,正如我上面所说的,如果没有发送keepalive,则根本没有内存丢失.
这就是我想告诉你们的全部内容;欢迎提出任何建议,欢迎任何关于 Qt 中 TCP 怪癖的说明,基本上任何内容。我在这方面花了很多天,但在这个时刻我只是被挡在了墙外。
【问题讨论】:
您写了很多击键,似乎表明您可以生成一个不错的简短MCVE 以消除所有疑问,但您是否真的尝试在单线程MCVE 中重现该问题?跨度> 我看到五个我们没有代码的函数调用(4 个带有m_pTcpSocket
和AssembleMsg
)。其中任何一个都可能是罪魁祸首。你能添加这些代码吗?
人们,这个问题不会在一个简短的例子中重现。这可能是我在这之外做的事情;但我们谈论的是非常多的代码行——该应用程序大约有 55k 行代码,这还不包括 Qt 生成的任何内容。并且有很多来回的 TCP 和传入的 UDP。如果这里不明显,其他地方就更不明显了。它当然可以在其他地方——我确实认为我做得对,我只是没有想法。我觉得提供赏金有点不好,我想我可能是在浪费人们的时间。但我没有办法撤回它。
你试过用valgrind吗?
@fyngyrz 关于感觉不好:对于剩余的赏金时间,您可以在问题顶部添加注释,说明您已经自己解决了问题并且赏金将不会被授予。或者,您可以将赏金分配给那些表示无法使用您提供的信息解决问题的响应者(如果这是正确的答案)。
【参考方案1】:
我找到了。从非 gui 线程中提取以非常间接的方式破坏了 Qt。停止这样做,它停止泄漏。谢谢大家。
值得称赞的是@Shf,但遗憾的是,我不太了解赏金,我可能告诉他进入这里并回答为时已晚。我会补偿他——当他得到我的信息时——在他实际提供关键提示的问题上提供赏金。赏金将包括我的堆栈溢出代表的其余部分,包括这个问题所获得的收益。我现在能做的最好的;下次我会知道的更好。这绝对是有教育意义的。
【讨论】:
在 Qt 中与 UI 的通信只能由信号和槽执行,这将保护您免受多线程错误的影响。 Qt newtworp 部分的设计也不需要线程。 我很高兴将赏金传递给@shf。我认为如果他发布答案,我就可以开始赏金并奖励它。但我没有看到任何答案......? SO赏金系统确实是雷区! 你真好。他还没有回复我——我给他留下了一些指示。应该是新年假期吧。 :)【参考方案2】:在代码方面还不够,但我会看看这些东西:-
你怎么知道你有内存泄漏? 你怎么知道这实际上不是堆损坏 看不到任何“新”或“删除”。如果您不使用它们,则“泄漏”可能是在 TCP 处理中。 套接字:尝试关闭它并每隔一段时间重新打开。这样做时泄漏是否会被清理干净? 你读入RBuf
,然后从Buf
组装...?
RBuf
是什么类型?为什么不检查您读入的数量?
Wireshark - 查看您的套接字上发送/接收的内容 - 那里发生了任何异常情况。或者,任何东西都会进入其他套接字。
您实际上是在从套接字读取字节吗?查看read
的返回值,查看this question。
【讨论】:
【参考方案3】:可以在线性影响泄漏率的事物中找到重要线索。您提到了keepalive消息,我认为这些消息是由您的应用程序发送的,而不是由您的应用程序接收的。
从发送方,您展示了如何发送单条消息,而不是如何管理外发消息队列。因此,我的建议是检查消息是否在发送后被正确删除,或者在管理该数据结构时是否存在其他问题。
【讨论】:
【参考方案4】:您显示和描述的代码似乎没有泄漏。
由于 Valgrind 不起作用,接下来最好的方法是尝试 LeakSanitizer (http://clang.llvm.org/docs/LeakSanitizer.html) 和/或 AddressSanitizer (http://clang.llvm.org/docs/AddressSanitizer.html)。见鬼,尽可能使用所有的消毒剂,也许会出事。
除此之外,我从代码中得到的唯一线索是pMsg
的处理:它是如何分配和解除分配的?我们没有看到代码。如果您愿意,请检查或分享。
【讨论】:
【参考方案5】:没有必要使用多线程。 查看我的other answer。它完美匹配您的问题,并将终止您的多线程问题。
在 Qt 中也总是使用信号和槽。默认情况下,它们可以保护代码免受跨线程问题的影响,并具有更多优势。
【讨论】:
这里需要线程,否则太多的事情被打包到 GUI 线程中,应用程序无法成功运行。在任何情况下,反正不是 TCP 线程在做 GUI 操作。所以谢谢,但不,谢谢。正如我所说,问题解决了。 你完全错了。您的应用程序设计只是损坏了。阻塞线程始终是一个糟糕的解决方案。同样在 Qt 中,线程之间的通信应该通过使用信号和槽来执行,这简化了很多并且是安全的。 Here you have 关于多线程的一些提示。 你做到了,你的代码:if (m_pTcpSocket->waitForReadyRead(100))
它是 100 毫秒,但它仍然是阻塞的。这不会通过我公司的代码审查。
这不是块。那是暂停。在该等待期间,如果没有任何事情可做,线程将被挂起。 CPU 返回核心池进行其他活动,直到有新数据或计时器超时。如果有数据,它会立即返回。如果无事可做,代码在不暂停自身的情况下循环是没有意义的。以上是关于Qt 4.7:TCP线程,数据传输导致内存泄漏的主要内容,如果未能解决你的问题,请参考以下文章
Qt 4.8 endInsert/RemoveRows 导致内存泄漏?
QTcpSocket 在工作进程中连续写入。避免内存泄漏的最佳实践