繁体   English   中英

在Qt信号中发射QVector参考导致复制

[英]Emitting QVector reference in Qt signal results in copy

我正在尝试通过构建与线扫描相机对话的应用程序来尝试。 最终,我希望每100毫秒从QThread (数据获取)到QRunnable (数据处理)传递384x128 unsigned short值的“块”(即数组)。 这意味着QRunnable将在下一个块到达之前有100ms的时间处理数据。

我仍然不确定移动数据的正确方法。 现在,我正在使用QVector。 在Qt4中,我理解隐式共享是指在信号被写入之前不会复制QVector直到对象被写入。 但是,在我编写的一个小型测试应用程序中,我不确定我确切理解这是什么意思。 这是下面提供的MWE的输出...

Acquire thread: init.
Acquire thread: block acquired: 0x106485e78 Content: {1, 2, 3, 4}
GUI thread: received signal: 0x7fff5fbfda98 Content: {1, 2, 3, 4}
Acquire thread: block acquired: 0x106485e78 Content: {1, 2, 3, 4}
GUI thread: received signal: 0x7fff5fbfda98 Content: {1, 2, 3, 4}
Acquire thread: block acquired: 0x106485e78 Content: {1, 2, 3, 4}
GUI thread: received signal: 0x7fff5fbfda98 Content: {1, 2, 3, 4}

我正在使用带有四个值的“虚拟” QVector,并在线程运行时跟踪矢量的地址。 数据在整个过程中都是正确的,但似乎已复制。 我不会在应用程序中的任何时候更改数据...只是显示。 我一直在尝试使用const QVector<unsigned short> ,使用引用的各种迭代等等。地址总是在变化。 由于性能在这里很重要,因此我担心在384 * 128值时制作QVector的副本。

同样,在另一个SO问题中,我正在努力弄清楚如何使QRunnable接收数据(在此示例中全部忽略了)。 但是,在这里要注意这一点很重要,因为我的想法是让QRunnable对image_buffer在QThread中的image_buffer引用进行操作。 我只是还没有弄清楚该怎么做。

具体问题:

-为什么会出现副本? 这是因为多线程吗?

-是否必须在整个过程中明确使用引用(即image_buffer&),从而完全删除QVector? 或者,我应该担心复印件吗?

-根据我的情况,从QThread到QRunnable传递数据的正确方法是什么?

下面的MWE演示不同的地址。 免责声明:我正在学习C ++。 没有接受过正式培训,只有几本好书摆在我面前。

main.cpp中

#include <QApplication>
#include <QMetaType>
#include <QVector>

#include "appwidget.h"

int main(int argc, char* argv[]) {

    QApplication app(argc, argv);

    AppWidget gui;
    gui.show();

    qRegisterMetaType<QVector<unsigned short> >("QVector<unsigned short>");

    return app.exec();
}

** appwidget.h **

#ifndef APPWIDGET_H
#define APPWIDGET_H

#include <QWidget>
#include <QVector>

#include "acquire.h"

class AppWidget : public QWidget
{ Q_OBJECT

 public:
  AppWidget(QWidget *parent = 0);

 protected:
  Acquire thread;

 public slots:
  void processBlock(QVector<unsigned short>);

};

#endif

appwidget.cpp

#include <QtGui>
#include <iostream>

#include "appwidget.h"

AppWidget::AppWidget(QWidget *parent)
    : QWidget(parent)
{

    thread.liftoff();
    connect(&thread, SIGNAL(blockAcquired(QVector<unsigned short>)), this, SLOT(processBlock(QVector<unsigned short>)));

    setWindowTitle(tr("TestApp"));
    resize(550, 400);

}

void AppWidget::processBlock(QVector<unsigned short> display_buffer)
{
    std::cout << "GUI thread: received signal: " 
          << &display_buffer 
          << " Content: {" 
          << display_buffer.at(0) << ", " 
          << display_buffer.at(1) << ", "
          << display_buffer.at(2) << ", "
          << display_buffer.at(3)
          << "}" << std::endl;

}

acquire.h

#ifndef ACQUIRE_H
#define ACQUIRE_H

#include <QVector>
#include <QThread>

class Acquire : public QThread {

  Q_OBJECT

 public:
    Acquire(QObject *parent = 0);
    ~Acquire();
    QVector<unsigned short> display_buffer;

    void liftoff();

 signals:
    void blockAcquired(QVector<unsigned short>);

 protected:
    void run();

 private:

};

#endif

acquire.cpp

#include <iostream>
#include <time.h>
#include <stdlib.h>

#include "acquire.h"

Acquire::Acquire(QObject *parent)
     : QThread(parent)
{
}

Acquire::~Acquire()
{
    std::cout << "Acquire thread: dying." << std::endl;
    wait();
}

void Acquire::liftoff()
{
    std::cout << "Acquire thread: init." << std::endl;
    start();
}

void Acquire::run()
{
    QVector<unsigned short> display_buffer(2 * 2);

    forever {

    /* 
       display_buffer will ultimate be a memcpy of image_buffer
       .. image_buffer updated continuously, 1 line every 800000ns x 128 lines
    */

    display_buffer[0] = 1;
    display_buffer[1] = 2;
    display_buffer[2] = 3;
    display_buffer[3] = 4;
    nanosleep((struct timespec[]){{0, 800000*128}}, NULL);

    std::cout << "Acquire thread: block acquired: " 
          << &display_buffer 
          << " Content: {" 
          << display_buffer.at(0) << ", " 
          << display_buffer.at(1) << ", "
          << display_buffer.at(2) << ", "
          << display_buffer.at(3)
          << "}" << std::endl;

    emit blockAcquired(display_buffer);

    }
}

在这种情况下,将进行复制,这是因为您按值传递,并且因为线程边界上的信号已排队。 不过,这很好,因为隐式共享意味着它们是浅表副本 如果原件和副本都只用于读取,则几乎没有复制开销。

不幸的是,您的程序实际上并非如此。 当信号发射后回绕时,您的永远循环将修改向量。 在此示例中,它实际上不会更改向量中的任何内容,因为您始终只是分配1,2,3,4,但是调用non-const operator []足以触发深层复制。

我的结论是:您可以是同步的,并且在读取器和写入器之间共享相同的缓冲区,也可以是异步的,并将写入缓冲区的副本提供给读取器。 您不能异步,并且不能在读者和作家之间共享相同的缓冲区。

对于异步处理,您处理此问题的方式似乎很好。 根据数据生成和数据处理的特征,您可能会(或可能不会)找到更好的同步解决方案。 使异步代码同步的最简单方法是在connect上将连接类型提供为Qt::BlockingQueuedConnection

要回答第二个问题,您可以使用QObjectQRunnable创建多重继承(只需确保QObject始终是列表中的第一个)。 然后,您可以使用信号/插槽机制完全按照测试示例中的方式传递数据。

class DataProcessor : public QObject, public QRunnable
{ Q_OBJECT
public:

public slots:
   void processBlock(QVector<unsigned short>);
};

实际上,您也可以将这种结构用于Acquire类,因为您的目标是在不同的线程中运行代码,而不是向线程本身添加额外的功能。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM