Qt线程之间发送大量数据
我有一个 QThread
,它会定期生成大量数据(每秒几兆字节),并且需要将其传输到父 (GUI) 线程.
I have a QThread
which generates a fairly large amount of data regularly (couple of megabytes per second), and it needs to transmit it to the parent (GUI) thread.
恐怕我对 QThread
的内部运作不是很确定,所以我想寻求最佳实践.
I'm afraid I'm not that certain in the inner workings of QThread
so I would like to ask for a best practice.
显然,传输数据最直接的方式就是emit
一个数组.然而,这有多有效?Qt是否知道它的使用位置并避免在发送和接收时进行深度复制?
Obviously, the most direct way to transmit data is to just emit
an array. However, how efficient is this? Does Qt know about where it is used and avoids deep copying it when sending and receiving it?
如果没有,我很乐意只在主线程中分配内存并提供一个指向子线程的指针,它将在其中写入数据(并且只emit
关于进度的短消息).这对我来说似乎不是最优雅的解决方案,这就是我问的原因.
If not, I can gladly just allocate the memory in the main thread and give a pointer to the child thread where it will write the data (and only emit
short messages about the progress). This does not seem to be the most elegant solution to me, this is why I'm asking.
如果Qt在发送和接收时避免复制多个缓冲区中的数据,是否在所有系统中都能保证?我没有资源尝试在各种操作系统下对其进行基准测试.
If Qt avoids copying the data in multiple buffers when emitting and receiving, is it guaranteed in all systems? I don't have the resources to try benchmarking it under various OSs.
推荐答案
QThread
的内部工作无关紧要:它们在事件循环的工作方式中没有任何作用.当您在 QObject
中发出
一个信号,该信号位于与插槽对象不同的线程中时,该信号将作为 QMetaCallEvent
发布到接收线程的事件队列.然后,在接收线程中运行的事件循环将对该事件执行操作,并在连接到所发出信号的槽中执行调用.
QThread
's inner workings are irrelevant: they play no role in how the event loops work. When you emit
a signal in a QObject
that lives in a thread different from the slot's object, the signal will be posted as a QMetaCallEvent
to the event queue of the receiving thread. The event loop running in the receiving thread will then act on this event and execute the call into the slot that was connected to the emitted signal.
因此,无论发生什么,您通过信号发送的任何数据最终都将作为 QEvent 派生类实例中的有效负载结束.
So, no matter what happens, whatever data you send through the signal will eventually end up as a payload in an instance of QEvent-derived class.
问题的关键在于当 QMetaCallEvent
到达事件循环并且容器作为参数被传递到槽中时.当然,复制构造函数可以在此过程中多次调用.下面是一些简单的代码,演示了复制构造函数和默认构造函数实际上被调用了多少次
The meat of the issue is when the QMetaCallEvent
reaches the event loop and the container gets passed into the slot as an argument. Of course the copy constructors could be called plenty of times along the way. Below is some simple code that demonstrates how many times the copy constructor and default constructor are in fact called
关于隐式共享的写时复制容器 (QVector) 的数据成员的元素,
on the elements of the data members of an implicitly shared copy-on-write container (QVector),
在代表容器的自定义类上.
on a custom class that stands in for a container.
你会惊喜的 :)
由于 Qt 容器是隐式共享的写时复制,因此它们的复制构造成本可以忽略不计:所做的只是引用计数器在构造时自动递增.例如,不会复制任何数据成员.
Since Qt containers are implicitly shared copy-on-write, their copy construction has negligible cost: all that's done is a reference counter is incremented atomically on construction. None of the data members are copied, for example.
唉,11 岁之前的 C++ 显示了它丑陋的一面:如果槽代码以任何方式修改了容器,则无法以这种方式传递对槽的引用,让编译器知道原始容器不是不再需要了.因此:如果插槽收到对容器的 const 引用,则可以保证不会制作任何副本.如果槽接收到容器的可写副本并且你修改了它,将会有一个完全不必要的副本,因为调用站点上的实例不再需要.在 C++-11 中,您将传递一个右值引用作为参数.在函数调用中传递右值引用会结束调用者中传递对象的生命周期.
Alas, pre-11 C++ shows its ugly side: if the slot code modifies the container in any way, there's no way to pass references to the slot in such a way that would let the compiler know that the original container is not needed anymore. Thus: if the slot receives a const reference to the container, you're guaranteed that no copies will be made. If the slot receives a writeable copy of the container and you modify it, there will be a completely unnecessary copy made since the instance alive at the call site is no longer needed. In C++-11 you'd pass an rvalue reference as a parameter. Passing an rvalue reference in a function call ends the lifetime of the passed object in the caller.
示例代码输出:
"Started" copies: 0 assignments: 0 default instances: 0
"Created Foo" copies: 0 assignments: 0 default instances: 100
"Created Bar" copies: 0 assignments: 0 default instances: 100
"Received signal w/const container" copies: 0 assignments: 0 default instances: 100
"Received signal w/copy of the container" copies: 0 assignments: 0 default instances: 100
"Made a copy" copies: 100 assignments: 1 default instances: 101
"Reset" copies: 0 assignments: 0 default instances: 0
"Received signal w/const class" copies: 2 assignments: 0 default instances: 1
"Received signal w/copy of the class" copies: 3 assignments: 0 default instances: 1
//main.cpp
#include <QtCore>
class Class {
static QAtomicInt m_copies;
static QAtomicInt m_assignments;
static QAtomicInt m_instances;
public:
Class() { m_instances.fetchAndAddOrdered(1); }
Class(const Class &) { m_copies.fetchAndAddOrdered(1); }
Class & operator=(const Class &) { m_assignments.fetchAndAddOrdered(1); return *this; }
static void dump(const QString & s = QString()) {
qDebug() << s << "copies:" << m_copies << "assignments:" << m_assignments << "default instances:" << m_instances;
}
static void reset() {
m_copies = 0;
m_assignments = 0;
m_instances = 0;
}
};
QAtomicInt Class::m_instances;
QAtomicInt Class::m_copies;
QAtomicInt Class::m_assignments;
typedef QVector<Class> Vector;
Q_DECLARE_METATYPE(Vector)
class Foo : public QObject
{
Q_OBJECT
Vector v;
public:
Foo() : v(100) {}
signals:
void containerSignal(const Vector &);
void classSignal(const Class &);
public slots:
void sendContainer() { emit containerSignal(v); }
void sendClass() { emit classSignal(Class()); }
};
class Bar : public QObject
{
Q_OBJECT
public:
Bar() {}
signals:
void containerDone();
void classDone();
public slots:
void containerSlotConst(const Vector &) {
Class::dump("Received signal w/const container");
}
void containerSlot(Vector v) {
Class::dump("Received signal w/copy of the container");
v[99] = Class();
Class::dump("Made a copy");
Class::reset();
Class::dump("Reset");
emit containerDone();
}
void classSlotConst(const Class &) {
Class::dump("Received signal w/const class");
}
void classSlot(Class) {
Class::dump("Received signal w/copy of the class");
emit classDone();
//QThread::currentThread()->quit();
}
};
int main(int argc, char ** argv)
{
QCoreApplication a(argc, argv);
qRegisterMetaType<Vector>("Vector");
qRegisterMetaType<Class>("Class");
Class::dump("Started");
QThread thread;
Foo foo;
Bar bar;
Class::dump("Created Foo");
bar.moveToThread(&thread);
Class::dump("Created Bar");
QObject::connect(&thread, SIGNAL(started()), &foo, SLOT(sendContainer()));
QObject::connect(&foo, SIGNAL(containerSignal(Vector)), &bar, SLOT(containerSlotConst(Vector)));
QObject::connect(&foo, SIGNAL(containerSignal(Vector)), &bar, SLOT(containerSlot(Vector)));
QObject::connect(&bar, SIGNAL(containerDone()), &foo, SLOT(sendClass()));
QObject::connect(&foo, SIGNAL(classSignal(Class)), &bar, SLOT(classSlotConst(Class)));
QObject::connect(&foo, SIGNAL(classSignal(Class)), &bar, SLOT(classSlot(Class)));
QObject::connect(&bar, SIGNAL(classDone()), &thread, SLOT(quit()));
QObject::connect(&thread, SIGNAL(finished()), &a, SLOT(quit()));
thread.start();
a.exec();
thread.wait();
}
#include "main.moc"
相关文章