I am going around in circles and hope someone can help. I have a much more complicated routine but have been able to distill it, mostly, so I can post the code.
The core of the problem is I am using poppler-qt to render PDF's, and that provides a QImage as output of a page. I am rendering in many threads at once, and trying to pass the images back to the main thread via signals/slots. This is modeled, somewhat, from the Mandelbrot example, though it immediately loads the QImage into a Pixmap and does not use it again. I need to save many of them.
These are loaded to an array of QImage*, and when I want to replace one (e.g. different scaling) I want to appropriately delete the old. I say appropriately as it is a bit unclear if I need to delete it manually, but if I do not it leaks memory (hugely so, not subtle, as shown in this loop below).
If I delete it, I get a segmentation fault and crash. I have read other threads about this, but without clear solution (most steered to alternative approaches).
It seems specific to the threading; if I do a fake threaded routine instead (i.e. call on the main thread), still using a signal/slot, the delete works. That routine is shown also but the call commented.
The failure is a bit fragile in this distilled version. Some little things, like removing a widget update, can make it work, sometimes, at least for a while. But it is almost completely reproducible for me in this code. Build has c++11 added as a config option.
This is happening with QT 5.5.1 (GCC 5.2.1 20151129) on Ubuntu 16.04 in a HyperV VM Guest, with 16GB memory and 3 cores. It uses the distro version of everything involved, and is up to date as of today.
The program is written to also be a stress test of sorts, and continue requesting pages rapidly on the main thread; it never uses them, it just deletes them when the next is requested.
I've left in a few comments showing some alternatives, e.g. allocating the QImage in the thread with new vs a local copy, that seem to make no difference (other than a need to adjust from pointers to non in the associated code).
As best I can tell, other than the possible shallow copy of the QImage, I do not think the worker thread is accessing anything that the primary thread is using. I have read about the shallow copy feature of QImage, and the double buffered aspect of the signal, but without it really helping me understand if this is a permitted mechanism or not (but then there's that official sample that seems to do the same).
My assumption is the failure is due to deleting something that no longer exists -- but if so, why does it build up memory usage indefinitely if I do not delete it? Is that double buffering perhaps breaking the reference count links?
Is there a more appropriate way to get that QImage back to the main thread? Should I pass in the address of where I want it, and let the thread load it directly? (and protect it with a mutex?)?
If anyone is bored enough to run the code, does it work for you, i.e. fail?
Thanks,
Linwood
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QLabel>
#include <QImage>
#include <QWidget>
#include <QTimer>
#include <QApplication>
#include "renderthread.h"
{
Q_OBJECT
public:
void doStuff();
private:
void requestPage();
int pagesRequested;
int pagesRetrieved;
renderThread *pageThreads;
bool pageThreadActive;
private slots:
void updateImage
(const QImage &image
);
};
#endif // MAINWINDOW_H
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QLabel>
#include <QImage>
#include <QWidget>
#include <QTimer>
#include <QApplication>
#include "renderthread.h"
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = 0);
void doStuff();
private:
QLabel outer;
QTimer t;
void requestPage();
const QImage *pageImages;
int pagesRequested;
int pagesRetrieved;
renderThread *pageThreads;
bool pageThreadActive;
private slots:
void updateImage(const QImage &image);
};
#endif // MAINWINDOW_H
To copy to clipboard, switch view to plain text mode
renderthread.h
#ifndef RENDERTHREAD_H
#define RENDERTHREAD_H
#include <QThread>
#include <QMutex>
#include <QImage>
#include <QWaitCondition>
#include <cassert>
class renderThread
: public QThread{
Q_OBJECT
public:
void render();
void fake_run();
signals:
void renderedImage
(const QImage &image
);
protected:
void run() Q_DECL_OVERRIDE;
private:
// QImage theImage;
};
#endif // RENDERTHREAD_H
#ifndef RENDERTHREAD_H
#define RENDERTHREAD_H
#include <QThread>
#include <QMutex>
#include <QImage>
#include <QWaitCondition>
#include <cassert>
class renderThread : public QThread
{
Q_OBJECT
public:
renderThread(QObject *parent = 0);
void render();
void fake_run();
signals:
void renderedImage(const QImage &image);
protected:
void run() Q_DECL_OVERRIDE;
private:
QMutex mutex;
QWaitCondition condition;
// QImage theImage;
QImage *theImagePtr;
};
#endif // RENDERTHREAD_H
To copy to clipboard, switch view to plain text mode
main.cpp
#include <QApplication>
#include "mainwindow.h"
int main(int argc, char *argv[])
{
MainWindow w;
w.setMinimumSize(600,20);
w.show();
return a.exec();
}
#include <QApplication>
#include "mainwindow.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.setMinimumSize(600,20);
w.show();
return a.exec();
}
To copy to clipboard, switch view to plain text mode
mainwindow.cpp
#include "mainwindow.h"
{
outer.setParent(this);
this->setCentralWidget(&outer);
pageImages = NULL;
pageThreads = new renderThread();
connect(pageThreads,
SIGNAL(renderedImage
(const QImage &)),
this,
SLOT(updateImage
(const QImage &)) );
pageThreadActive=false;
pagesRequested=0;
pagesRetrieved=0;
t.setInterval(1);
connect(&t,&QTimer::timeout, this, [this]{this->doStuff();});
t.start();
}
void MainWindow::doStuff()
{
requestPage();
outer.
setText(QString("Requested=%1, Retrieved=%2").
arg(pagesRequested
).
arg(pagesRetrieved
));
outer.update(); // If this is removed the delete works also.
}
void MainWindow::requestPage()
{
if(!pageThreadActive)
{
pageThreadActive=true;
//pageThreads->fake_run(); // this does a retrieve without using the thread, and works with the delete in place
pageThreads->render(); // << this threaded one works only if delete is not in place below
pagesRequested++;
}
}
// Slot
void MainWindow
::updateImage(const QImage &image
) {
if(pageImages)
{
delete pageImages; // << If removed no crash but bad memory leak, if in place crash (sometimes)
pageImages=NULL;
}
pageImages= ℑ
pageThreadActive = false;
pagesRetrieved++;
}
#include "mainwindow.h"
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
{
outer.setParent(this);
this->setCentralWidget(&outer);
pageImages = NULL;
pageThreads = new renderThread();
connect(pageThreads, SIGNAL(renderedImage(const QImage &)), this, SLOT(updateImage(const QImage &)) );
pageThreadActive=false;
pagesRequested=0;
pagesRetrieved=0;
t.setInterval(1);
connect(&t,&QTimer::timeout, this, [this]{this->doStuff();});
t.start();
}
void MainWindow::doStuff()
{
requestPage();
outer.setText(QString("Requested=%1, Retrieved=%2").arg(pagesRequested).arg(pagesRetrieved));
outer.update(); // If this is removed the delete works also.
}
void MainWindow::requestPage()
{
if(!pageThreadActive)
{
pageThreadActive=true;
//pageThreads->fake_run(); // this does a retrieve without using the thread, and works with the delete in place
pageThreads->render(); // << this threaded one works only if delete is not in place below
pagesRequested++;
}
}
// Slot
void MainWindow::updateImage(const QImage &image)
{
if(pageImages)
{
delete pageImages; // << If removed no crash but bad memory leak, if in place crash (sometimes)
pageImages=NULL;
}
pageImages= ℑ
pageThreadActive = false;
pagesRetrieved++;
}
To copy to clipboard, switch view to plain text mode
renderthread.cpp
#include "renderthread.h"
{
}
void renderThread::render()
{
mutex.lock(); // shouldn't be necessary but want to make sure the emit completes before we issue wakeOne
if(!isRunning()) start(LowPriority);
else condition.wakeOne();
mutex.unlock();
return;
}
void renderThread::run()
{
forever
{
// theImage = QImage(1000,1000,QImage::Format_ARGB32_Premultiplied); // same thing happens if declaration is not a pointer and appropriate other changes made
theImagePtr
= new QImage(1000,
1000,
QImage::Format_ARGB32_Premultiplied);
mutex.lock();
emit renderedImage(*theImagePtr);
condition.wait(&mutex);
mutex.unlock();
}
}
void renderThread::fake_run()
{
theImagePtr
= new QImage(1000,
1000,
QImage::Format_ARGB32_Premultiplied);
emit renderedImage(*theImagePtr);
}
#include "renderthread.h"
renderThread::renderThread(QObject *parent): QThread(parent)
{
}
void renderThread::render()
{
mutex.lock(); // shouldn't be necessary but want to make sure the emit completes before we issue wakeOne
if(!isRunning()) start(LowPriority);
else condition.wakeOne();
mutex.unlock();
return;
}
void renderThread::run()
{
forever
{
// theImage = QImage(1000,1000,QImage::Format_ARGB32_Premultiplied); // same thing happens if declaration is not a pointer and appropriate other changes made
theImagePtr = new QImage(1000,1000,QImage::Format_ARGB32_Premultiplied);
mutex.lock();
emit renderedImage(*theImagePtr);
condition.wait(&mutex);
mutex.unlock();
}
}
void renderThread::fake_run()
{
theImagePtr = new QImage(1000,1000,QImage::Format_ARGB32_Premultiplied);
emit renderedImage(*theImagePtr);
}
To copy to clipboard, switch view to plain text mode
Bookmarks