【问题标题】:Programm parallel QThread is creating a memory leak on application quit程序并行 QThread 在应用程序退出时创建内存泄漏
【发布时间】:2018-11-25 14:19:49
【问题描述】:

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

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

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

这是我的最小代码: 线程类.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
            // 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 很糟糕,但似乎很好地模拟了我的主程序的行为:

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

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

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

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-&gt;quit(),现在调用了ThreadClass 的析构函数,但my_thread-&gt;wait() 永远不会完成。

【问题讨论】:

  • 出口处的泄漏通常没什么大不了的。整个过程即将结束,因此操作系统无论如何都会回收所有内存。
  • @JesperJuhl 我不确定那个,但很高兴知道。但我还是想解决这个问题。我认为这根本不应该发生。
  • @JesperJuhl 这是真的,但如果不调用析构函数负责刷新文件或取消分配共享内存区域等事情,泄漏可能很危险
  • @cbuchart 我知道。这就是为什么我说“通常”。

标签: c++ qt memory-leaks qthread visual-leak-detector


【解决方案1】:

问题描述:

ThreadHandler 的析构函数从主线程发出stopThread 时,Qt 通过将事件发布到工作线程的事件循环(也称为排队连接)来调用连接的槽(&amp;ThreadClass::stop)。这意味着当此信号发出时,worker 的事件循环需要准备好接收新事件(因为您依赖它来执行适当的清理)。但是,正如您已经发现的那样,对thread-&gt;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::finishedspecial way 中处理:

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

这意味着你可以使用thread-&gt;quit()(和你做的一样),但你只需要添加:

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

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


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

【讨论】:

  • 但我的实际线程不仅仅是一个繁重的功能。我想在未来添加更多功能,比如QFileSystemWatcher。线程应该只通过关闭程序来停止。因此,线程永远不会“完成”,我需要告诉他:现在停止。
  • 这正是使用我的回答中描述的模式可以解决的问题。通过将线程的deleteLater 插槽连接到线程的finished 信号,您可以在线程退出时安排尽可能多的工作人员进行删除。这种方法绝对不限于一名工人拥有一项繁重的功能。
  • 哦。现在我明白了,你的意思。我调用thread-&gt;quit(),它发出my_thread::finished,然后调用my_threaded_class::stop。看起来还不错。但是,如果my_threaded_class 没有完成,它是如何工作的? Atm,我故意用所有工作阻塞我的事件循环,这样stop 调用将在所有工作完成后最后处理。 my_thread 会等待课程结束吗?
  • 经过测试并且可以正常工作。这绝对是比我更好的解决方案。我将使用新的完整代码更新我的答案,并接受您的答案作为解决方案。
  • my_thread 保证在跳出事件循环后调用连接到QThread::finished 的所有槽。你可以(我会说,你应该)在thread-&gt;quit() 的析构函数中调用thread-&gt;wait() 之后调用ThreadHandler,以使主线程等待后台线程,直到它完成所有必要的清理
【解决方案2】:

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

removed

这是 Mike 新实施的解决方案。这很容易实现,我只需要在threadhandler 类中更改与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);
            // https://stackoverflow.com/questions/53468408
            QObject::connect(my_thread, &QThread::finished, 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;
};

【讨论】:

    猜你喜欢
    • 2011-08-22
    • 2014-03-02
    • 2011-08-26
    • 2012-10-18
    • 1970-01-01
    • 2016-03-28
    • 2010-11-27
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多