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"
假设我有一个信号 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"