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

Emitting QVector reference in Qt signal results in copy

本文关键字:复制 参考 QVector Qt 信号 发射      更新时间:2023-10-16

我正在尝试通过构建一个应用程序来与线扫描相机交谈。 最终,我想每 100 毫秒传递一个 384x128 unsigned short值的"块"(即数组(,从QThread(数据采集(传递到QRunnable(数据处理(。 这意味着QRunnable在下一个区块到达之前将有 100 毫秒的时间来处理数据。

我仍然不确定移动数据的正确方法。 现在,我正在使用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&(,从而完全删除QVector? 或者,我应该担心副本吗?

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

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

主.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

应用小部件.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;
}

获取.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

收购.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 运算符 [] 足以触发深层复制。

我的结论是:你可以是同步的,并在读取

器和写入器之间共享相同的缓冲区,或者你可以是异步的,并将写入缓冲区的副本提供给读取器。您不能是异步的,并且在读取器和编写器之间共享相同的缓冲区。

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

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

class DataProcessor : public QObject, public QRunnable
{ Q_OBJECT
public:
public slots:
   void processBlock(QVector<unsigned short>);
};

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