I am having trouble trying to figure out where this performance degrade in my codes comes from. My program is quite big so I'll simplify using some examples. Say I have a MainUI class subclassed from QMainWindow, which has two member objects, a Worker (subclassed from QThread) and a DisplayPanel (subclassed from QGLWidget). Then a connection is established between a signal from this Worker object (emitted from its run method) and a slot in the DisplayPanel object. And I see performance degrade in the run method if I increase the number of connections.
Basically what I am describing is (very simplified):
class Worker : public QThread {
Q_OBJECT
signals:
void updateDisplay();
protected:
void startWorking() {
// some setup codes
this->start();
}
void run() {
while (1) {
// some processing codes
emit updateDisplay();
}
}
};
class DisplayPanel : public QOpenGLWidget, protected QOpenGLFunctions_5_0_Core {
Q_OBJECT
public slots:
void refresh() {
update();
}
};
class MainUI : public QMainWindow {
Q_OBJECT
public:
explicit MainUI(QWidget *parent = 0) {
// some Ui setup codes
worker = new Worker();
num_panels = 5 // arbitrary number
panels.resize(num_panels);
for (auto p : panels) {
p = new DisplayPanel();
connect(worker, SIGNAL(updateDisplay()), p, SLOT(refresh()));
}
}
void process() {
worker->startWorking();
}
private:
unsigned num_panels;
Worker* worker;
std::vector<DisplayPanel*> panels;
};
Imagine MainUI::process() was called from the GUI.
What confuses me is that there is some noticeable performance degrade in the while loop of Worker::run() if I increase MainUI::num_panels. From my understanding, the increased number of signals-and-slots connections should not affect the performance of Worker very much because the queued requests to DisplayPanel::update() should run on a different thread.
Any insights would be appreciated!
Related
Currently I have two classes that look something like this:
class Worker : public QObject
{
Q_OBJECT
bool aborted = false;
public:
Worker() : QObject() {}
public slots:
void abort() { aborted = true; }
void doWork()
{
while(!aborted && !work_finished)
{
//do work
QCoreApplication::processEvents();
}
}
};
class Controller : public QObject
{
Q_OBJECT
QThread workerThread;
public:
Controller() : QObject()
{
Worker *worker = new Worker;
worker->moveToThread(&workerThread);
connect(&workerThread, &QThread::finished, worker, &Worker::deleteLater);
connect(this, &Controller::startWork, worker, &Worker::doWork);
connect(this, &Controller::aborted, worker, &Worker::abort);
}
signals:
void startWork();
void aborted();
};
Controller *cont = new Controller;
emit cont->startWork(); // Start the loop
emit cont->aborted(); // Stop the loop
So the idea is that there is a loop running in a Worker thread, which can be stopped from a Controller thread.
In the example this is done by calling QCoreApplication::processEvents(), which allows signals to call slots before returning control to the loop.
It's important the loop is only stopped at the start or end of an iteration.
Although this works nicely, I think QCoreApplication::processEvents() is pretty expensive, at least when used inside a very long loop (up to thousands in practice).
So my question is, how can I achieve the same result in a better/cheaper way?
There are three alternative solutions that I'm aware of at this time.
1. QThread::requestInterruption (suggested by #Felix)
According to QThread::isInterruptionRequested:
Take care not to call it too often, to keep the overhead low.
Whereas QCoreApplication::processEvents makes no remark on performance or memory usage, so I don't think QThread::requestInterruption is an improvement over QCoreApplication::processEvents in this case.
2. std::atomic (suggested by #Felix)
The main characteristic of atomic objects is that access to this contained value from different threads cannot cause data races [...]
The boolean can be stored inside a std::atomic which can be made a member of the Controller class instead of the Worker class. Then we need to pass a reference to aborted to and store it in Worker, and set it to true from Controller when needed.
I didn't fully test this approach, so please correct me if I got something wrong.
class Worker : public QObject {
Q_OBJECT
std::atomic<bool> &aborted;
public:
Worker(std::atomic<bool> &aborted) : QObject(), aborted(aborted) {}
public slots:
void doWork() {
while(!aborted.load() && !work_finished) /* do work */
}
};
class Controller : public QObject {
Q_OBJECT
QThread workerThread;
std::atomic<bool> aborted;
public:
Controller() : QObject() {
aborted.store(false);
Worker *worker = new Worker(aborted);
worker->moveToThread(&workerThread);
connect(&workerThread, &QThread::finished, worker, &Worker::deleteLater);
connect(this, &Controller::startWork, worker, &Worker::doWork);
connect(this, &Controller::aborted, worker, &Worker::abort);
}
void abort() { aborted.store(true); }
signals:
void startWork();
};
Controller *cont = new Controller;
emit cont->startWork(); // Start the loop
cont->abort(); // Stop the loop
3. QWaitCondition & QMutex
A boolean paused will be needed. Controller and Worker need read/write access to it.
Set paused to true in Controller when needed.
During the loop in Worker, if(paused): QWaitCondition::wait() until QWaitCondition::wakeAll() is called from the calling thread.
QMutex::lock will need to be called whenever paused is accessed.
class Worker : public QObject {
Q_OBJECT
bool &aborted, &paused;
QWaitCondition &waitCond;
QMutex &mutex;
public:
Worker(bool &aborted, bool &paused, QWaitCondition &waitCond, QQMutex &mutex)
: QObject(), aborted(aborted), paused(paused), waitCond(waitCond), mutex(mutex) {}
public slots:
void doWork() {
while(!aborted && !work_finished) {
//do work
mutex.lock();
if(paused) {
waitCond.wait(&mutex);
paused = false;
}
mutex.unlock();
}
}
void abort() { aborted = true; }
};
class Controller : public QObject {
Q_OBJECT
bool aborted=false, paused=false;
QWaitCondition waitCond;
QMutex mutex;
QThread workerThread;
public:
Controller() : QObject() {
Worker *worker = new Worker(aborted, paused, waitCond, mutex);
worker->moveToThread(&workerThread);
connect(&workerThread, &QThread::finished, worker, &Worker::deleteLater);
connect(this, &Controller::startWork, worker, &Worker::doWork);
}
void abort() {
mutex.lock();
paused = true; // Worker starts waiting
mutex.unlock();
if(confirmed_by_user) aborted = true; // Don't need to lock because Worker is waiting
waitCond.wakeAll(); // Worker resumes loop
}
signals:
void startWork();
};
Controller *cont = new Controller();
emit cont->startWork(); // Start the loop
cont->abort(); // Stop the loop
Is it possible to implement function interrupt in Qt (5.x).
For example if I have a button and want something to execute on the thread (which is running infinite loop) when this button is clicked, I could say something like this:
in thread...
forever
{
if(button_is_pressed_flag)
{
do something...
}
}
is there a better way?
The infinite loop should be an event loop, and then it can automatically process cross-thread slot calls without you worrying about the details.
The idiom to run code "continuously" on an event loop is the zero-duration timer.
Let's say you start with code that looks like this:
class MyThread : public QThread {
bool button_is_clicked_flag = false;
void run() override {
forever{
if (button_is_clicked_flag) {
onButtonClick();
button_is_clicked_flag = false;
}
doWork();
}
}
void onButtonClick();
void doWork();
public:
using QThread::QThread;
void setButtonClickedFlag();
}
int main(int argc, char **argv) {
...
MyThread t;
t.start();
...
}
It is required for doWork() not to take too long - nor too short. If it took ~5ms on modern hardware, it'd be just about a right tradeoff between overhead and latency for a general-purpose application. If you need lower latency reaction in the worker thread, then doWork() must do less work. It probably doesn't make much sense for doWork() to take much less than 1ms.
And whenever doWork() doesn't have anything to do, e.g. if it's done with the computation it was supposed to perform, it should stop the timer that keeps it alive.
You should transform it to look as follows:
class MyWorker : public QObject {
Q_OBJECT
QBasicTimer m_timer;
void doWork();
void timerEvent(QTimerEvent *event) {
if (event->timerId() == m_timer.timerId())
doWork();
}
public:
explicit MyWorker(QObject *parent = nullptr) : QObject(parent) {
m_timer.start(0, this);
}
Q_SLOT void onButtonClick() {
// Ensure we're invoked correctly
Q_ASSERT(QThread::currentThread() == thread());
...
}
}
class Window : public QWidget {
Ui::Window ui;
public:
Q_SIGNAL void buttonClicked();
explicit Window(QWidget *parent = nullptr) : QWidget(parent) {
ui.setupUi(this);
connect(ui.button, &QPushButton::clicked, this, &Window::buttonClicked);
}
};
class SafeThread : public QThread {
Q_OBJECT
using QThread::run; // final method
public:
~SafeThread() { quit(); wait(); } // we're safe to destroy - always
};
int main(int argc, char **argv) {
...
MyWorker worker;
SafeThread thread;
Window window;
// onButtonClick will be executed in worker->thread()
connect(&window, &Window::buttonClicked, &worker, &MyWorker::onButtonClick);
worker.moveToThread(&thread);
thread.start();
window.show();
return app.exec();
}
The event loop that runs in QThread::run will continuously invoke doWork via the timer event handler. But whenever a cross-thread slot call needs to be made to an object living in that thread, the event loop will deliver the internal QMetaCallEvent representing the slot call to QObject::event, which will then execute the call.
Thus, when you set a breakpoint in onButtonClick, there will be QObject::event nearby on the call stack.
You could start a thread and then immediately wait on a std::condition_variable, then when the button is clicked (the event being called on the main thread), notify the condition variable and the thread would awake.
However, this is a bit strange. What are you trying to do? call an asynchronous task upon a button click? In that case, perhaps it would be better just to start one from the button click event with std::packaged_task or std::async.
I’m using this connector to RabbitMQ:
https://github.com/fuCtor/QAMQP
I need performance and multi-thread in my application. Please, can I use multi-thread with this connector?
I tried:
void Test::newMessage(QAMQP::Queue * q) {
while (q->hasMessage()) {
QAMQP::MessagePtr message = q->getMessage();
MyEvent *me = new MyEvent();
me->message = message;
poolThreadPosicao->start(me);
}
}
class MyEvent : public QRunnable {
public:
QAMQP::MessagePtr message;
void run() {
s.queue->ack(this->message);
}
};
In some messages the RabbitMQ say: “Unacked 10 messages”. The 10 messages is my qos in broker message. What I need solve this? How to do?
First of all I recommend that you switch to https://github.com/mbroadst/qamqp, as it is the replacement for the original project (which is no longer in active development). The updated code contains many performance and memory enhancements, as well as more complete support for RabbitMQ. Having said that, currently both versions of the project are aimed at having one connection per thread. This means that any of the Channels that you create (an Exchange, or a Queue), will be parented to the connection (Client) which created it and therefore are bound to the creating thread.
One way to handle the problem you are facing would be to inherit from QRunnable and QObject, emitting the message when you have completed your task (NOTE: this is untested, I'm just giving the basic structure):
using namespace QAMQP;
class MessageJob : public QRunnable, public QObject
{
Q_OBJECT
public:
MessageJob(const Message &message)
: m_message(message)
{
}
virtual void run() {
// process the message
// when you are done, emit the finished signal
Q_EMIT finished(m_message);
}
Q_SIGNALS:
void finished(const Message &message);
private:
Message m_message;
};
class Test : public QObject
{
Q_OBJECT
public:
Test(QObject *parent = 0)
: QObject(parent)
{
// setup and connect client
// create queue and start consuming
}
private Q_SLOTS:
void messageReceived(const Message &message)
{
MessageJob *job = new MessageJob; // no parent, this will be autodeleted
connect(job, SIGNAL(finished(Message)), this, SLOT(jobFinished(Message)), Qt::QueuedConnection);
// NOTE: Qt::QueuedConnection is very important as it allows the signal to
// cross threads
QThreadPool::globalInstance()->start(job);
}
void jobFinished(const Message &message) {
m_queue->ack(message);
}
private:
Client m_client;
Queue *m_queue;
};
I'm learning Qt and I was reading about Threads, Events and QObjects from Qt wiki, and followed the wiki recommendations on how to handle some work in a while condition but its not working for my specific case. Here's a simple example of what I'm currently trying to achieve.
class FooEvents : public FooWrapper {
public virtual serverTime(..) { std::cout << "Server time event\n"; }
public virtual connected(..) { std::cout << "Connected event\n"; }
}
class Foo : public QObject {
private:
FooAPI *client;
public:
Foo(FooEvents *ev, QObject *parent = 0) : client(new FooApi(ev)) { .. }
private slots:
void processMessages() {
if (state is IDLE)
reqFooAPiServerTime();
select(client->fd()+1, ...);
if (socket is ready for read)
client.onReceive();
}
public:
void connect(...) {
if (connection) {
QObject::connect(&timer, SIGNAL(timeout()), this, SLOT(processMessages()));
timer.start(1000); // I don't get the output from FooEvents
}
}
}
This is a very simple but I think it illustrates my case. Why is this not working and what other alternatives to I have to handle this case? Thanks.s
Edit: The processMessages is being called every second but I don't get any output from the events
Where is timer declared and defined?
If it's local to Foo::connect() it'll be destroyed before it ever has a chance to fire. Presumably it just needs to be a member object of the Foo class.
Also keep in mind that QObject provides it's own simple interface to a timer - just override the protected virtual timerEvent() function and call QObject's startTimer() to start getting those timer events. In this case instead of having a slot to receive the timer events, they will just end up at the overridden timerEvent() function:
protected:
void timerEvent(QTimerEvent *event) {
processMessages();
}
public:
void connect( /* ... */ ) {
// ...
startTimer(1000);
}
This won't work, because processMessages() is not a SLOT.
So Declare processMessages() as a private slot and then try.
You don't declare the timer neither the slot. In the header you must declare:
class ... {
QTimer timer;
...
private slots:
void processMessages();
...
};
Then remember to make the SIGNAL-SLOT connection and configure the timer:
connect(&timer, SIGNAL(timeout()), this, SLOT(processMessages()));
timer.setInterval(1000);
timer.start();
Also timer.start(1000); would be valid...
ANOTHER POSSIBILITY
Other possibility would be to use the timer associated with each Q_OBJECT and overload the timerEvent:
class ... {
Q_OBJECT
...
protected:
void timerEvent(QTimerEvent *event);
...
};
Then you must implement the timer event as this:
void MyClass::timerEvent(QTimerEvent *event) {
processMessages();
}
And you can configure the timer with a simple call to startTimer(1000);
I have 5 classes that interact (maintaining professionally, not author). My problem is that the code that is emitting the signal (there is just one, code below) is never activating the slot (MyWidget::HandleMeasurementChanged). This system has a large degree of complexity. I have tried to reduce that, but think the complexity likely contributes to the problem. There is also a high rate of calls to Observer::notify, but most of these will get filtered out by code that I have not posted here and the Emit calls are fairly rare. If anyone could help point me to why the slot is not getting activated, I'd really appreciate it. It is almost acting like the MyWidget class instance is not processing its event loop. I have had a little success setting the connect type to Direct Connection, but since the emit is in a separate thread and the production code for the slot will update the UI I have ruled that out as a final solution.
class IObserver { public: virtual void notify()=0; };
class ExternalMeasurement { ... };
class Measurement { public: Measurement(ExternalMeasurement source); };
class Observer : public QThread, public IObserver
{
signals:
void MeasurementChanged(boost::shared_ptr<Measurement> measurement);
public:
//called by 3rd party in separate thread
virtual void notify(ExternalMeasurement measurement)
{
_measurement_ =
boost::shared_ptr<Measurement>(new Measurement(measurement));
emit MeasurementChanged(_measurement);
}
private:
boost::shared_ptr<Measurement> _measurement_;
};
class MyWidget : public QWidget
{
private:
Component _component_;
public slots:
void HandleMeasurementChanged(boost::shared_ptr<Measurement> measurement);
public:
MyWidget(Component * component_)
};
MyWidget::MyWidget(Component * component_)
{
_component_ = component_;
connect(
_component_->_observer_,
MeasurementChanged(boost::shared_ptr<Measurement> measurement),
this,
HandleMeasurementChanged(boost::shared_ptr<Measurement> measurement));
}
class Component
{
private:
QApplication * _application_;
MyWidget * _widget_;
Observer * _observer_;
public:
void MainFunc();
}
void Component::MainFunc()
{
_observer_ = new Observer();
...
_application_ = new QApplication(...);
...
_widget_ = new MyWidget(...);
...
_widget_->show();
_application_->exec();
}
This was referenced in the link that Jeremy added in a comment to my question, but just for clarity:
The solution was to add:
qRegisterMetaType<shared_ptr<Measurement> >("shared_ptr<Measurement>");
immediately before the call to connect.