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

时间:2013-10-01 16:00:38

标签: c++ qt qthread qtcore

我正试图通过构建应用程序来与线扫描相机交谈。最后,我想从unsigned short(数据采集)到QThread(数据处理)每100毫秒传递一个384x128 QRunnable值的“块”(即数组)。这意味着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在引用上运行到QThread中的image_buffer。我只是想不出怎么做。

具体问题:

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

- 我是否必须始终明确使用引用(即image_buffer&amp;),从而完全删除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);

    }
}

2 个答案:

答案 0 :(得分:3)

在这种情况下将复制,因为您正在传递值,并且因为跨线程边界的信号已排队。这很好,因为implicit-sharing意味着它们是浅层副本。如果原始文件和副本仅用于阅读,则几乎没有复制的开销。

不幸的是,你的程序实际上并非如此。你的永久循环将在信号发射后循环回来时修改矢量。在这个例子中,它实际上不会改变向量中的任何东西,因为你总是只分配1,2,3,4,但调用非const运算符[]足以触发深层复制。

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

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

答案 1 :(得分:0)

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

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

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

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