【问题标题】:qt serial port memory leakqt串口内存泄漏
【发布时间】:2016-07-05 10:11:41
【问题描述】:

我使用以下代码与 USB 串行端口设备通信:

#include "masterthread.h"
#include <QtSerialPort/QSerialPort>
#include <QTime>
#include "Windows.h"
#include "Psapi.h"
#include <QDebug>
QT_USE_NAMESPACE

MasterThread::MasterThread(QObject *parent)
: QThread(parent), waitTimeout(0), quit(false)
{
}

MasterThread::~MasterThread()
{
    mutex.lock();
    quit = true;
    cond.wakeOne();
    mutex.unlock();
    wait();
}

void MasterThread::run()
{
    bool currentPortNameChanged = false;

    QSerialPort serial;
    serial.setPortName("COM3");
    serial.setBaudRate(57600);
    serial.setStopBits(static_cast<QSerialPort::StopBits>(1));
    serial.setDataBits(static_cast<QSerialPort::DataBits>(8));
    serial.setParity(static_cast<QSerialPort::Parity>(0));
    serial.open(QIODevice::ReadWrite);

    //Tell the serial port connected device to start talking
    //--------------------------------------
    const char init[] = { 0x0d, 0x0d, 0x0d };
    serial.write(init, sizeof(init));
    const char* cmd = "mavlink stop\n";
    serial.write(cmd, strlen(cmd));
    serial.write(init, 2);
    cmd = "uorb start";
    serial.write(cmd, strlen(cmd));
    serial.write(init, 2);
    cmd = "sh /etc/init.d/rc.usb\n";
    serial.write(cmd, strlen(cmd));
    serial.write(init, 4);
    serial.waitForBytesWritten(100);

    int i = 0;
    int j = 0;
    forever
    {

        //Write test data out
        //-----------------------------
        QByteArray test(2000, 't');
        serial.write(test);
        bool check = serial.waitForBytesWritten(100);
        if (!check)
        {
            qDebug() << "FAIL: " << j++;
        }

        if (serial.waitForReadyRead(20))
        {
            QByteArray responseData = serial.readAll();
            while (serial.waitForReadyRead(10))
                responseData += serial.readAll();

            QString response(responseData);
            qDebug() << response;
        }
        QThread::msleep(20);

        //Print memory usage
        //---------------------------------------------------
        if (i++ % 10 == 0)
        {
            PROCESS_MEMORY_COUNTERS memcount;
            if (!GetProcessMemoryInfo(GetCurrentProcess(), &memcount, sizeof(memcount))) return;
            qDebug()<<"----------------------------" << memcount.WorkingSetSize / 1024 << "KB memory used";
        }
    } // end foever

    qDebug() << "Exiting forever loop";
}

用一个简单的 main.cpp 作为:

#include <QApplication>
#include "masterthread.h"

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

    MasterThread thread;
    thread.start();
    return app.exec();
}

但内存使用量不断增加,就像每小时 5~10MB 一样,好像有一些泄漏。 该设备应该连接数天和数周......

我在这里做错了什么?我在Qt5.6 windows7调试

【问题讨论】:

  • 如果你在一个带有Qt消息循环的线程中运行,那么你可以在QThread::msleep(20);之前调用QCoreApplication::processEvents();
  • @evilruff 如果某些 Qt 对象在内部使用 deleteLater() 或者其他一些依赖事件循环的机制,我可以看到它导致这种行为。我不太了解 Qt,无法准确知道哪些对象依赖于事件循环,哪些不依赖,但如果您有更多经验,请随时分享您的想法。
  • 例如,processEvents 将防止此代码受到 Qt 5.5 中的此错误的影响 bugreports.qt.io/browse/QTBUG-48653
  • @Nyaruko 它似乎仍然在内部使用其他东西,例如带有QTimer 的异步信号,这可能仍然可以解释内存,这只是一个例子。你是说即使偶尔调用QCoreApplication::processEvents();,明显的内存泄漏仍然存在?
  • 绝对没有理由通过重新实现线程来实现此代码。重构:将其全部放入QObject,forever 部分属于从零超时计时器调用的槽/函子,然后摆脱waitFor 并使用信号/槽连接到QIODevice 信号。这将导致更清洁、更高效、声明式的代码。请参阅例如this answer 或 that answer 获取灵感。

标签: c++ qt


【解决方案1】:

许多 Qt 组件对其事件循环都有隐式依赖。

当您通过调用app.exec(); 启动主线程事件循环时,您并未处理由在QThread MasterThread thread; 中创建的QObjects 生成的事件。 Qt 中事件处理的细节和细微差别在此页面上有很好的描述:https://wiki.qt.io/Threads_Events_QObjects#Threads_and_QObjects

但解决方案归结为:如果您希望能够在处理一些长时间运行的任务的线程中处理排队的 Qt 事件,您应该不时调用QCoreApplication::processEvents();。这将防止 Qt 事件无休止地排队。

【讨论】:

  • 再次,在 run() 中调用 QCoreApplication::processEvents() 通常安全吗?假设对应的线程没有附加任何插槽。
  • @Nyaruko yes processEvents 是线程安全的,如果调用线程没有 Qt 事件循环(即,如果您使用原始 std::thread 或其他不是QThread)。
  • 但是如果我使用没有事件循环的 QThread 可以吗? (我重新实现了run()函数)
  • @Nyaruko 我不确定这怎么可能。一旦您在其中使用某些(或者可能全部,尚未检查)Qt 对象或调用QThread::currentThread(),Qt 就会自动“采用”线程。您可以尝试手动使用moveToThread 将您创建的所有对象移动到主线程的事件循环中。但是你必须检查你正在使用的 QObjects 是否是线程安全的(因为它们的插槽将在另一个线程中执行)
【解决方案2】:

查看代码 Qt 5.7,5.6,5.5 并阅读文档后编辑。

由于答案已经被接受,我只想在这里添加一些想法,因为它对于 cmets 来说太长了。

保持简短 - 你接受的答案是错误的..

故事有两个方面。由于 SO 的答案经常被认为是“只要它们有效”,我想解释一下自己......

如果您查看提供的代码 - 它没有任何问题。所有对象都已正确分配堆栈并应自动销毁。

重点是 QtSerial 使用 deleteLater() ,然后是一个问题 - 如何正确删除这些分配。

如果任何模块/对象/代码使用 deleteLater() 它需要一个事件循环,如果 deleteLater() 在没有事件循环的线程上调用,对象将在线程终止后被删除。只要上面的代码没有运行事件循环,processEvents 就不起作用.. 实际上 processEvents() 不是用于此的东西,因为从称为 deleteLater() 的上下文返回的整个想法并有一个next 运行,并且在 Qt 源代码中进行了检查,因此在不增加循环计数的情况下直接调用 processEvent() 将什么都不做,这就是为什么你接受的答案是完全错误的。

结论:

如果任何对象需要运行事件循环,则应在文档中明确说明,因为在事件循环之外以同步模式使用 QIODevice 没有任何问题。

所以在我看来,重点是 - 它是 QT 序列本身中的一个错误,我建议您报告。

一般来说,Qt 运行永无止境的循环确实是错误的做法。 使用推送到线程的 QObject Worker 策略要好得多,也更干净,有适当的偶数循环运行等。

对于小型“线程”任务,最好使用 QtConcurrent。

正确的解决方法:

您将拥有一个运行正常的事件循环的线程和一个在 20 毫秒触发的计时器来完成您的工作

// main thread:
class Worker: public QObject {
    public:
       Worker();

    public slots:

       onInit() {
          // initialize everything
          startTimer(20);
       }

    protected:

       void timerEvent(..) {
          // do your things every 20ms
       }   
  }

  ...

  QThread * pWorkerThread = new QThread();
  pWorkerThread->setObjectName(QString("Serial"));

  Worker * pWorker = new Worker();
  Worker->setObjectName(QString("Common Storage Impl"));
  Worker->moveToThread(WorkerThread);

  connect(pWorkerThread, SIGNAL(started()), pWorker, SLOT(onInit()));
  connect(pWorkerThread, SIGNAL(finished()), pWorker, SLOT(deleteLater()));
  connect(pWorkerThread, SIGNAL(finished()), pWorkerThread, SLOT(deleteLater()));

  pWorkerThread->start();

  ...

【讨论】:

  • 我同意,但您所说的副作用可能是什么?你能举个例子,这样我就可以在临时解决这个问题时避免它们。
  • @evilruff 我对 deleteLater 的评论可能是错误的,我的回答没有提及。事实上,我认为 deleteLater 除了在 QSerialPort 的析构函数中被调用,在这种情况下显然不会累积。
  • 好吧,我改变了我的答案..我认为它是 deleteLater(),因为我简要地看了一下,Serial 使用 QTimer,它可能很容易使用 deleteLater(),但我仍然会将此问题解决给 Qt Bugtracker ..无论如何 processEvents 根本不会破坏 deleteLaters() ..
  • 但问题是,在我添加了 processEvents 调用之后,就没有更多的泄漏了吗?
  • 正如我所写,它是非常特定于 Qt 版本的。我试图解释为什么它是错误的方法,但使用与否取决于你。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-05-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-04-30
  • 1970-01-01
相关资源
最近更新 更多