程序并行QThread在应用程序退出时造成内存泄漏 [英] Programm parallel QThread is creating a memory leak on application quit

查看:625
本文介绍了程序并行QThread在应用程序退出时造成内存泄漏的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个更大的项目,带有GUI,我想在后台管理一些文件.我为此任务实现了一个新线程,并且在运行时,一切正常.但是,一旦我退出应用程序,visual-leak-detector就会发现3-7个内存泄漏.

I have a bigger project, with a GUI and I want to manage some files in the background. I've implemented a new thread for this task and on runtime, everything works great. But as soon as I quit the application visual-leak-detector finds 3-7 memory leaks.

我分离了线程代码并创建了一个新项目,以最少的代码示例进行检查,但是我仍然无法解决问题.

I separated my threading code and created a new project to check this with a minimal code example, but I'm still not able to fix my issue.

我认为这与主程序的事件循环有关.也许循环不会处理最后的事件以删除我的线程类和线程本身.因为我停止并退出了析构函数中的线程.但是我不确定这一点.

I think it has something to do with the event loop of the main program. Maybe the loop doesn't process the last events to delete my thread class and the thread itself. Because I stop and quit the thread in a destructor. But I'm not sure on this one.

这是我的最小代码: threadclass.hpp:

Here is my minimal code: threadclass.hpp:

#include <QObject>
#include <QDebug>

class ThreadClass : public QObject {
    Q_OBJECT

public:
    explicit ThreadClass() {}
    virtual ~ThreadClass(){
        qDebug() << "ThreadClass Destructor";
    }

signals:
    // emit finished for event loop
    void finished();

public slots:
    // scan and index all files in lib folder
    void scanAll(){
        for(long i = 0; i < 10000; i++){
            for (long k = 0; k < 1000000; k++);
            if(i%500 == 0)
                qDebug() << "thread: " << i;
        }
    }
    // finish event loop and terminate
    void stop(){
        // will be processed after scanall is finished
        qDebug() << "STOP SIGNAL --> EMIT FINSIHED";
        emit finished();
    }
};

threadhandler.hpp:

threadhandler.hpp:

#include <QObject>
#include <QThread>
#include "threadclass.hpp"

class ThreadHandler : public QObject {
    Q_OBJECT

public:
    explicit ThreadHandler(QObject *parent = 0) : parent(parent), my_thread(Q_NULLPTR) {}

    virtual ~ThreadHandler() {
        // TODO Check!
        // I think I don't have to delete the threads, because delete later
        // on finish signal. Maybe I just have to wait, but then how do I
        // check, if thread is not finished? Do I need to make a bool var again?

        if (my_thread != Q_NULLPTR && my_thread->isRunning())
        {
            emit stopThread();
            //my_thread->quit();
            my_thread->wait();
            //delete my_thread;
        }

        qDebug() << "ThreadHandler Destructor";
        my_thread->dumpObjectInfo();
    }

    void startThread(){
        if (my_thread == Q_NULLPTR)
        {
            my_thread = new QThread;
            ThreadClass *my_threaded_class = new ThreadClass();
            my_threaded_class->moveToThread(my_thread);

            // start and finish
            QObject::connect(my_thread, &QThread::started, my_threaded_class, &ThreadClass::scanAll);
            QObject::connect(this, &ThreadHandler::stopThread, my_threaded_class, &ThreadClass::stop);

            // finish cascade
            // https://stackoverflow.com/a/21597042/6411540
            QObject::connect(my_threaded_class, &ThreadClass::finished, my_threaded_class, &ThreadClass::deleteLater);
            QObject::connect(my_threaded_class, &ThreadClass::destroyed, my_thread, &QThread::quit);
            QObject::connect(my_thread, &QThread::finished, my_thread, &QThread::deleteLater);

            my_thread->start();
        }
    }

signals:
    void stopThread();

private:
    QObject *parent;
    QThread* my_thread;
};

main.cpp很糟糕,但似乎可以很好地模拟我的主程序的行为:

The main.cpp is crappy but seems to simulate the behavior of my main program well enough:

#include <QCoreApplication>
#include <QTime>
#include <QDebug>
#include "threadhandler.hpp"

#include <vld.h>

int main(int argc, char *argv[]) {
    QCoreApplication a(argc, argv);

    ThreadHandler *th = new ThreadHandler();
    th->startThread();

    // wait (main gui programm)
    QTime dieTime= QTime::currentTime().addSecs(5);
    while (QTime::currentTime() < dieTime) {
        QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
    }

    qDebug() << "DELETE TH";
    delete th;
    qDebug() << "FINISH ALL EVENTS";
    QCoreApplication::processEvents(QEventLoop::AllEvents, 500);
    qDebug() << "QUIT";
    QCoreApplication::quit();
    qDebug() << "CLOSE APP";
    // pause console
    getchar();
//    return a.exec();
}

这是VLD的输出:

WARNING: Visual Leak Detector detected memory leaks!
...
turns out this is very boring and uninteresting
...
Visual Leak Detector detected 3 memory leaks (228 bytes).
Largest number used: 608 bytes.
Total allocations: 608 bytes.
Visual Leak Detector is now exiting.
The program '[8708] SOMinimalExampleThreads.exe' has exited with code 0 (0x0).

更新1:我在ThreadClass的析构函数中添加了qDebug() << "ThreadClass Destructor";,新的输出如下所示:

Update 1: I added qDebug() << "ThreadClass Destructor"; to the destructor of the ThreadClass and my new output looks like this:

...
thread:  9996
thread:  9997
thread:  9998
thread:  9999
ThreadHandler Destructor
FINISH ALL EVENTS
CLOSE APP

现在很明显,我的线程类的析构函数从未被调用过,因此会在void中丢失.但是,为什么这不起作用?

Now it is clear that the destructor of my threaded class is never called and therefore lost in the void. But then why is this not working?

QObject::connect(my_threaded_class, &ThreadClass::finished, my_threaded_class, &ThreadClass::deleteLater);

更新2:我在ThreadHandler中发现了一个问题:

Update 2: I found one problem in ThreadHandler:

emit stopThread();
my_thread->quit(); // <-- stops the event loop and therefore no deletelater
my_thread->wait();

我删除了my_thread->quit(),现在调用了ThreadClass的析构函数,但my_thread->wait()从未完成.

I removed my_thread->quit() and now the destructor of ThreadClass is called, but my_thread->wait() never finishes.

推荐答案

问题描述:

ThreadHandler的析构函数从主线程发出stopThread时,Qt通过将事件发布到工作线程的事件循环(又称为排队连接)中来调用连接的插槽(&ThreadClass::stop).这意味着在发出此信号时,工作人员的事件循环需要准备就绪以接收新事件(因为您依靠它来执行适当的清理).但是,正如您已经发现的那样,对thread->quit()的调用可能会导致事件循环比期望的更早退出(在辅助线程调用ThreadClass::stop之前,因此不会发出信号ThreadClass::finished) .您可能想要检查这个最小示例的输出,该示例重现了我正在谈论的行为:

Problem description:

When the destructor of ThreadHandler emits stopThread from the main thread, Qt invokes the connected slot (&ThreadClass::stop) by posting an event into the worker thread's event loop (aka, a queued connection). This means that the event loop of the worker's needs to be ready for receiving new events when this signal is emitted (since you are relying on it to perform proper clean-up). However, as you've already spotted, the call to thread->quit() might cause the event loop to quit earlier than desired (before the worker thread makes its way to call ThreadClass::stop, and hence the signal ThreadClass::finished is not emitted). You might want to examine the output of this minimal example that reproduces the behavior I am talking about:

#include <QtCore>

/// lives in a background thread, has a slot that receives an integer on which
/// some work needs to be done
struct WorkerObject : QObject {
  Q_OBJECT
public:
  using QObject::QObject;
  Q_SLOT void doWork(int x) {
    // heavy work in background thread
    QThread::msleep(100);
    qDebug() << "working with " << x << " ...";
  }
};

/// lives in the main thread, has a signal that should be connected to the
/// worker's doWork slot; to offload some work to the background thread
struct ControllerObject : QObject {
  Q_OBJECT
public:
  using QObject::QObject;
  Q_SIGNAL void sendWork(int x);
};

int main(int argc, char *argv[]) {
  QCoreApplication a(argc, argv);

  QThread thread;
  WorkerObject workerObj;
  workerObj.moveToThread(&thread);
  // quit application when worker thread finishes
  QObject::connect(&thread, &QThread::finished, &a, &QCoreApplication::quit);
  thread.start();

  ControllerObject controllerObj;
  QObject::connect(&controllerObj, &ControllerObject::sendWork, &workerObj,
                   &WorkerObject::doWork);

  for (int i = 0; i < 100; i++) {
    QThread::msleep(1);
    // call thread.quit() when i is equal to 10
    if (i == 10) {
      thread.quit();
    }
    controllerObj.sendWork(i);
  }
  return a.exec();
}

#include "main.moc"

在我的机器上,这是可能的输出:

On my machine, Here is a possible output:

working with  0  ...
working with  1  ...
working with  2  ...
working with  3  ...

请注意,尽管在主线程的第十次迭代中调用了thread.quit(),但工作线程可能不会处理退出调用之前收到的所有消息(并且我们将值3作为最后一个值由工作人员处理). *

Notice that, despite the fact that thread.quit() is called on the tenth iteration from the main thread, the worker thread might not process all messages received before the exit call (and we get the value 3 as the last value processed by the worker).*

实际上,由于信号QThread::finished是在

Actually, Qt provides a canonical to way to quit a worker thread and perform necessary clean-up, since the signal QThread::finished is handled in a special way:

发出此信号时,事件循环已经停止运行. 除了延迟删除事件外,该线程中将不再处理其他事件. 此信号可以连接到QObject :: deleteLater(),以释放该线程中的对象.

When this signal is emitted, the event loop has already stopped running. No more events will be processed in the thread, except for deferred deletion events. This signal can be connected to QObject::deleteLater(), to free objects in that thread.

这意味着您可以使用thread->quit()(与您以前的操作方式相同),但是只需添加:

This means that you can use thread->quit() (the same way you were doing), but you just need to add:

connect(my_thread, &QThread::finished, my_threaded_class, &ThreadClass::stop);

startThread,然后从析构函数中删除不必要的emit stopThread();.

to your startThread and remove the unnecessary emit stopThread(); from the destructor.

* 在文档中找不到详细解释此行为的页面,因此,我提供了这个最小的示例来解释我在说什么.

* I couldn't find any page in the documentation that explains this behavior in detail, so I provided this minimal example to explain what I am talking about.

这篇关于程序并行QThread在应用程序退出时造成内存泄漏的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆