Параллельная программа QThread создает утечку памяти при выходе из приложения
У меня есть большой проект с графическим интерфейсом, и я хочу управлять некоторыми файлами в фоновом режиме. Я реализовал новый поток для этой задачи, и во время выполнения все прекрасно работает. Но как только я выйду из приложения visual-leak-detector
находит 3-7 утечек памяти.
Я отделил свой многопоточный код и создал новый проект, чтобы проверить это на примере минимального кода, но я все еще не могу решить мою проблему.
Я думаю, что это как-то связано с циклом событий основной программы. Возможно, цикл не обрабатывает последние события для удаления моего класса потока и самого потока. Потому что я остановился и вышел из потока в деструкторе. Но я не уверен в этом.
Вот мой минимальный код: 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://stackru.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() << "ThreadClass Destructor";
деструктору ThreadClass
и мой новый вывод выглядит так:
...
thread: 9996
thread: 9997
thread: 9998
thread: 9999
ThreadHandler Destructor
FINISH ALL EVENTS
CLOSE APP
Теперь ясно, что деструктор моего многопоточного класса никогда не вызывается и поэтому теряется в пустоте. Но тогда почему это не работает?
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()
никогда не заканчивается
2 ответа
Описание проблемы:
Когда деструктор ThreadHandler
излучает stopThread
из основного потока Qt вызывает подключенный слот (&ThreadClass::stop
) путем публикации события в цикле событий рабочего потока (или в очереди). Это означает, что цикл обработки событий работника должен быть готов к приему новых событий, когда этот сигнал испускается (поскольку вы полагаетесь на него для выполнения надлежащей очистки). Однако, как вы уже заметили, призыв к 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
обрабатывается особым образом:
Когда этот сигнал испускается, цикл событий уже остановлен. Больше событий не будет обрабатываться в потоке, кроме отложенных событий удаления. Этот сигнал может быть подключен к QObject::deleteLater(), чтобы освободить объекты в этом потоке.
Это означает, что вы можете использовать thread->quit()
(так же, как вы это делали), но вам просто нужно добавить:
connect(my_thread, &QThread::finished, my_threaded_class, &ThreadClass::stop);
на ваш startThread
и удалить ненужное emit stopThread();
от деструктора.
* Я не смог найти ни одной страницы в документации, которая бы подробно объясняла это поведение, поэтому я привел этот минимальный пример, чтобы объяснить, о чем я говорю.
Я узнал, что my_thread->wait()
функция, блокирует цикл событий и, следовательно, выход и deleteLater
каскад никогда не заканчивается Я исправил это другим способом ожидания готового потока:
removed
Вот недавно реализованное решение от Майка. Это было довольно легко реализовать, мне нужно было только изменить соединение на my_threaded_class::stop
в threadhandler
учебный класс.
#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://stackru.com/questions/53468408
QObject::connect(my_thread, &QThread::finished, my_threaded_class, &ThreadClass::stop);
// finish cascade
// https://stackru.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;
};