在Qt线程之间发送大量数据 [英] Sending large amount of data between Qt threads

查看:89
本文介绍了在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?

如果没有,我可以很高兴地在主线程中分配内存,并提供一个指向子线程的指针,该子线程将在该子线程中写入数据(并且仅提供有关进度的短消息).对于我来说,这似乎并不是最优雅的解决方案,这就是为什么我要问这个问题.

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的内部工作方式无关紧要:它们在事件循环的工作方式中不起作用.当emit中的emit中的信号位于与插槽对象不同的线程中时,该信号将作为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),

代表容器的自定义类.

您会惊喜不已:)

由于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"

这篇关于在Qt线程之间发送大量数据的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆