Qt 信号参数线程安全

Qt signal argument thread safety

假设我有一个信号 sendImage(const QImage&) 连接到另一个线程中的插槽 updateLabel(const QImage&),它将 QImage 转换为 QPixmap,然后将其放入 QLabel。现在我想知道,如果我使用函数 const QImage& prepareImage() 作为信号的参数,例如emit sendImage(prepareImage()),信号每秒发出几十次,是线程安全的还是prepareImage和updateLabel同时访问图片出现race condition导致程序崩溃?

首先,我不太明白你的意思:

is there a possibility that a race condition occurs between prepareImage and updateLabel both accessing the image at the same time

一个线程创建了一个对象(让我们暂时假设这是同一个对象)另一个正在使用它。两个线程同时使用它的时刻到底在哪里?

即使发生这种情况,在您的情况下,第一个线程中创建的 QImage 也会被复制并传递给另一个线程,因此这不是同一个对象。

这取决于SIGNAL和SLOT之间的连接。

如果您使用的是默认值,即 Qt::AutoConnection,它的作用类似于 Qt::QueuedConnection 用于跨线程连接。

在队列连接中,所有信号参数都被复制到队列中并按值传递,即使您通过引用传递它们也是如此。

因此,不可能出现竞争条件。

注意:QImage 实现了 CopyOnWrite(隐式共享),这意味着如果您设法以某种方式更改 QImage 的内部缓冲区,您的同步将会崩溃。

值得庆幸的是,Qt 保护您免受您自己的伤害,并且会复制图像,这样您就不会搬起石头砸自己的脚。复制将在信号发射时完成,并且是在信号的实现内部完成的——这里,复制是在 Object::source 在调用堆栈上时完成的。

鉴于QImage是隐式共享的,初始拷贝会很便宜,但如果主线程随后修改源图像,它会强制深拷贝。如果您的修改导致源被丢弃,用新图像替换源图像会比 "modifying" 更有效。

输出:

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"