Programm parallel QThread 在应用程序退出时创建内存泄漏

Programm parallel QThread is creating a memory leak on application quit

我有一个更大的项目,有一个 GUI,我想在后台管理一些文件。我已经为此任务和运行时实现了一个新线程,一切都很好。但是一旦我退出应用程序 visual-leak-detector 发现 3-7 内存泄漏。

我分离了我的线程代码并创建了一个新项目来使用最少的代码示例来检查它,但我仍然无法解决我的问题。

我觉得跟主程序的事件循环有关系。也许循环不处理最后的事件来删除我的线程 class 和线程本身。因为我在析构函数中停止并退出线程。但是我不确定这个。

这是我的最小代码: 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:

#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
            // 
            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 很糟糕,但似乎足够好地模拟我的主程序的行为:

#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: 我将 qDebug() << "ThreadClass Destructor"; 添加到 ThreadClass 的析构函数中,我的新输出如下所示:

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

现在很明显,我的线程 class 的析构函数从未被调用,因此丢失了。但是为什么这不起作用?

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

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

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

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

我发现 my_thread->wait() 函数阻塞了事件循环,因此退出和 deleteLater 级联永远不会完成。 我用另一种等待完成线程的方法解决了这个问题:

removed

这是 Mike 新实施的解决方案。这很容易实现,我只需要在 threadhandler class.

中将连接更改为 my_threaded_class::stop
#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() {

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

        qDebug() << "ThreadHandler Destructor";
    }

    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(my_thread, &QThread::finished, my_threaded_class, &ThreadClass::stop);

            // finish cascade
            // 
            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;
};

问题描述:

ThreadHandler 的析构函数从主线程发出 stopThread 时,Qt 通过将事件发布到工作线程的事件循环(又名,排队的连接)。这意味着当发出此信号时,工作人员的事件循环需要准备好接收新事件(因为您依赖它来执行适当的清理)。但是,正如您已经发现的那样,对 thread->quit() 的调用可能会导致事件循环提前退出(在工作线程调用 ThreadClass::stop 之前退出,因此信号 ThreadClass::finished 未发出)。您可能想检查这个重现我正在谈论的行为的最小示例的输出:

#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"

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

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

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

解决方案:

实际上,Qt 提供了一种规范的方法来退出工作线程并执行必要的清理,因为信号 QThread::finished 是在 special way:

中处理的

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()(和你做的一样),但你只需要添加:

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

到你的 startThread 并从析构函数中删除不必要的 emit stopThread();


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