发送Qt的线程之间大量数据的(Sending large amount of data betwee

2019-06-25 22:51发布

我有一个QThread产生相当大量的数据的定期(夫妇每秒兆字节),并且需要将其发送到父(GUI)线程。

我怕我不是在内部运作,某些QThread ,所以我想问的最佳实践。

显然,要发送数据的最直接的方法是只emit阵列。 然而,如何高效这是什么? 请问Qt的了解它的使用和深拷贝发送和接收它时,它可避免?

如果没有,我可以很高兴地只是分配内存在主线程,并给予一个指向子线程在那里将写入数据(且仅emit有关进展情况的短消息)。 这似乎不是最优雅的解决方案对我来说,这就是为什么我问。

如果Qt的避免发射和接收时,它被在所有系统中保证复制在多个缓冲器中的数据? 我没有足够的资源来尝试下不同的操作系统基准它。

Answer 1:

QThread的内部运作是不相关的:它们在事件如何循环工作没有任何作用。 当emit的信号QObject生活在从槽的对象不同的螺纹,该信号将被张贴作为QMetaCallEvent到接收线程的事件队列。 然后在接收线程运行事件循环将作用于该事件,执行该呼叫到已连接到所发射的信号的插槽。

所以,无论发生什么事情,您可以通过发送信号的任何数据将最终结束了在QEvent的派生类的一个实例的有效载荷。

这个问题的肉是当QMetaCallEvent到达事件循环和容器被传递到插槽作为参数。 当然,拷贝构造函数可以调用大量的沿途倍。 以下是一个演示了多少次的拷贝构造函数和默认的构造函数实际上被称为一些简单的代码

  • 上的隐式共享写入时复制容器的数据成员的(QVector)中的元素,

  • 对一个容器处于一个自定义类。

你会惊喜:)

由于Qt容器是隐式共享写入时复制,他们的拷贝构造具有可忽略不计的成本:所有的完成是一个引用计数器递增原子建设。 数据成员的无被复制,例如。

可惜的是,前11 C ++显示出其丑陋的一面:如果段代码修改容器以任何方式,有没有办法通过这样的方式,它可以让编译器插槽引用知道,不需要再在原来的容器。 因此:如果插槽用于常量引用的容器,你保证,没有副本将会作出修改。 如果插槽用于容器的可写拷贝,并修改它,也将是自实例在调用点不再需要活着做了一个完全不必要的副本。 在C ++ - 11你会传递一个右值引用作为参数。 在函数调用传递一个rvalue参考结束在呼叫者传递的对象的生存期。

示例代码的输出:

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


Answer 2:

当通信大缓冲区,这是“传统”在生产线新的()缓冲区对象,当装起来,排队/ EMIT /无论*缓冲区消费者线程,并立即new()的一个又一个,(在同一*缓冲器VAR),用于数据的下一个负荷。

问题:如果你的GUI线程跟不上,你会除非你采取一些流量控制措施得到内存失控(如预分配*缓冲池和“循环”他们)。

我最常做的是在一个循环(在一个大服务器多达数千个)预分配一定的缓冲情况,并推动它们的实例在生产者 - 消费者“池队列”。 如果一个子线程想要从一些网络连接到缓冲区中加载数据,它必须弹出一个从池中,并加载它。 然后,它可以排队/ EMIT /不管缓冲区消费者线程并弹出另外一个缓冲区用于任何可能会在更多的数据。消费者线程得到缓冲溶液,处理数据,并推动“拿来主义”缓冲放回池中队列再利用。 这提供了流量控制:如果孩子线程负载缓冲区比消费者线程更快的处理它们,就会发现池为空并在其上阻塞,直到消费者线程返回一些使用缓冲,以便封盖缓存/内存使用(也避免持续的新的/处置,或在那些支持它)语言GC。

我喜欢转储池队列计数在1秒定时器的GUI状态栏 - 这让我看缓冲区的使用,(并迅速发现如果有泄漏:)。



文章来源: Sending large amount of data between Qt threads