Programm parallel QThread is creating a memory leak on application quit

…衆ロ難τιáo~ 提交于 2019-12-10 20:07:33

问题


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.

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:

#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;
};

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();
}

Here is the output from 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).

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

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);

Update 2: I found one problem in ThreadHandler:

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

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


回答1:


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  ...

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).*

Solution:

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:

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.

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);

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.




回答2:


I found out that the my_thread->wait() function, blocks the event loop and therefore the quit and deleteLater cascade is never finished. I fixed this with another method of waiting for the finished thread:

removed

Here is the newly implemented solution from Mike. It was pretty easy to implement, I only had to change the connection to my_threaded_class::stop in the threadhandler class.

#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;
};


来源:https://stackoverflow.com/questions/53468408/programm-parallel-qthread-is-creating-a-memory-leak-on-application-quit

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!