Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Qt signal argument thread safety

Suppose I have a signal sendImage(const QImage&) that is connected to a slot updateLabel(const QImage&) in an other thread, which will convert the QImage to a QPixmap then place it in a QLabel. Now I'm wondering, if I use a function const QImage& prepareImage() as the argument of the signal e.g. emit sendImage(prepareImage()), and the signal is emitted dozens of times per seconds, is it thread safe or is there a possibility that a race condition occurs between prepareImage and updateLabel both accessing the image at the same time thus crashing the program?

like image 482
user2563661 Avatar asked Dec 05 '25 14:12

user2563661


2 Answers

Thankfully, Qt protects you from yourself and will copy the image so that you don't shoot yourself in the foot. The copying will be done upon signal's emission, and is done from inside of the implementation of the signal - here, the copying is done when Object::source is on the call stack.

Given that a QImage is implicitly shared, the initial copy will be cheap, but if the main thread then modifies the source image, it will force a deep copy. If your modifications are such that the source is discarded, it'll be more efficient to replace the source image with the new one instead of "modifying" it.

Output:

data is at 0x7fff5fbffbf8 in main thread QThread(0x10250a700)
0x7fff5fbffbf8 was copied to 0x1025115d0 in thread QThread(0x10250a700)
got 0x1025115d0 in thread QThread(0x7fff5fbffb80)
#include <QCoreApplication>
#include <QDebug>
#include <QThread>

class Copyable {
public:
   Copyable() {}
   Copyable(const Copyable & src) {
      qDebug() << static_cast<const void*>(&src) << "was copied to"
               << static_cast<void*>(this) << "in thread" << QThread::currentThread();
   }
};
Q_DECLARE_METATYPE(Copyable)

class Object : public QObject {
   Q_OBJECT
public:
   Q_SIGNAL void source(const Copyable &);
   Q_SLOT void sink(const Copyable & data) {
      qDebug() << "got" << static_cast<const void*>(&data) << "in thread"
               << QThread::currentThread();
      // Queue a quit since we are racing with app.exec(). qApp->quit() is a no-op before
      // the app.exec() has had a chance to block.
      QMetaObject::invokeMethod(qApp, "quit", Qt::QueuedConnection);
   }
};

class Thread : public QThread { public: ~Thread() { quit(); wait(); } };

int main(int argc, char *argv[])
{
   QCoreApplication app(argc, argv);
   Copyable data;
   qDebug() << "data is at" << static_cast<void*>(&data) << "in main thread" << app.thread();
   qRegisterMetaType<Copyable>();
   Object o1, o2;
   Thread thread;
   o2.moveToThread(&thread);
   thread.start();
   o2.connect(&o1, &Object::source, &o2, &Object::sink);
   emit o1.source(data);
   return app.exec();
}

#include "main.moc"
like image 129
Kuba hasn't forgotten Monica Avatar answered Dec 07 '25 04:12

Kuba hasn't forgotten Monica


It depends on the connection between SIGNAL and SLOT.

If you are using the default one, which is Qt::AutoConnection, it acts like Qt::QueuedConnection for cross-thread connections.

In Queued Connection, all signal parameters are copied to the queue and passed by value even though you pass them by reference.

Hence, there is no possibility that a race condition occurs.

Note : QImage implements CopyOnWrite (Implicit Sharing) which means if you manage to change the internal buffer of QImage somehow, your synchronization will blow up.

like image 39
Murat Şeker Avatar answered Dec 07 '25 03:12

Murat Şeker