简体   繁体   English

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

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

I'm trying to slog my way through building an application to talk to a linescan camera. 我正在尝试通过构建与线扫描相机对话的应用程序来尝试。 Ultimately, I want to pass a "block" (ie, array) of 384x128 unsigned short values every 100ms from a QThread (data acquisition) to a QRunnable (data processing). 最终,我希望每100毫秒从QThread (数据获取)到QRunnable (数据处理)传递384x128 unsigned short值的“块”(即数组)。 This means the QRunnable will have 100ms to process the data before the next block arrives. 这意味着QRunnable将在下一个块到达之前有100ms的时间处理数据。

I'm still not sure the right way to move the data around. 我仍然不确定移动数据的正确方法。 Right now, I'm using a QVector. 现在,我正在使用QVector。 In Qt4, I understand implicit sharing to mean that a QVector would not be copied if emitted in a signal, until the object is written upon. 在Qt4中,我理解隐式共享是指在信号被写入之前不会复制QVector直到对象被写入。 However, in a small test application I made, I am not sure I understand exactly what that means. 但是,在我编写的一个小型测试应用程序中,我不确定我确切理解这是什么意思。 Here is the output of the MWE provided below... 这是下面提供的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}

I am using a "dummy" QVector with four values and tracking the address of the vector as the thread runs. 我正在使用带有四个值的“虚拟” QVector,并在线程运行时跟踪矢量的地址。 The data is correct throughout, but it seems a copy is made. 数据在整个过程中都是正确的,但似乎已复制。 I am not changing the data at any point in the application... just displaying. 我不会在应用程序中的任何时候更改数据...只是显示。 I've tried using const QVector<unsigned short> throughout, various iterations of using references, etc. The address always changes. 我一直在尝试使用const QVector<unsigned short> ,使用引用的各种迭代等等。地址总是在变化。 Since performance will be important here, I'm concerned about making copies of the QVector when 384*128 values. 由于性能在这里很重要,因此我担心在384 * 128值时制作QVector的副本。

Also, in another SO question, I am struggling with figuring out how to have the QRunnable receive the data (that is all left out of this example). 同样,在另一个SO问题中,我正在努力弄清楚如何使QRunnable接收数据(在此示例中全部忽略了)。 But, that is important to note here because my idea is to have the QRunnable operate on the reference to the image_buffer that lives in the QThread. 但是,在这里要注意这一点很重要,因为我的想法是让QRunnable对image_buffer在QThread中的image_buffer引用进行操作。 I just haven't figured out how to do that. 我只是还没有弄清楚该怎么做。

The specific questions: 具体问题:

-- Why does it appear a copy is made? -为什么会出现副本? Is this because of multithreading? 这是因为多线程吗?

-- Do I have to explicitly use references (ie, image_buffer&) throughout, thereby removing QVector entirely? -是否必须在整个过程中明确使用引用(即image_buffer&),从而完全删除QVector? Or, should I worry about the copies? 或者,我应该担心复印件吗?

-- What is the right way to pass data from QThread to QRunnable for my situation? -根据我的情况,从QThread到QRunnable传递数据的正确方法是什么?

MWE below to demonstrate the different addresses. 下面的MWE演示不同的地址。 Disclaimer: I'm learning C++ as I go. 免责声明:我正在学习C ++。 No formal training, just a few good books in front of me. 没有接受过正式培训,只有几本好书摆在我面前。

main.cpp 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 ** ** 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 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 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 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);

    }
}

Copies will be made in this case both because you are passing by value and because signals across thread boundaries are queued. 在这种情况下,将进行复制,这是因为您按值传递,并且因为线程边界上的信号已排队。 That's fine though, because implicit-sharing means they are shallow copies . 不过,这很好,因为隐式共享意味着它们是浅表副本 There's practically no overhead to copying if both the original and the copy are only used for reading. 如果原件和副本都只用于读取,则几乎没有复制开销。

Unfortunately, that's not actually the case in your program. 不幸的是,您的程序实际上并非如此。 Your forever loop will modify the vector when it loops back around after signal emission. 当信号发射后回绕时,您的永远循环将修改向量。 In this example, it won't actually change anything in the vector since you're always just assigning 1,2,3,4, but calling the non-const operator[] is enough to trigger the deep copy. 在此示例中,它实际上不会更改向量中的任何内容,因为您始终只是分配1,2,3,4,但是调用non-const operator []足以触发深层复制。

My conclusion is this: you can be synchronous and share the same buffer between your readers and writers, or you can be asynchronous and give a copy of your write buffer to your readers. 我的结论是:您可以是同步的,并且在读取器和写入器之间共享相同的缓冲区,也可以是异步的,并将写入缓冲区的副本提供给读取器。 You cannot be asynchronous and share the same buffer between your readers and your writers. 您不能异步,并且不能在读者和作家之间共享相同的缓冲区。

The way you're handling this seems fine for asynchronous processing. 对于异步处理,您处理此问题的方式似乎很好。 Depending on the characteristics of your data generation and data processing, you may (or may not) find a synchronous solution to be better. 根据数据生成和数据处理的特征,您可能会(或可能不会)找到更好的同步解决方案。 The easiest way to make your asynchronous code synchronous is to supply the connection type as Qt::BlockingQueuedConnection on connect . 使异步代码同步的最简单方法是在connect上将连接类型提供为Qt::BlockingQueuedConnection

To answer your second question, you can create a multiple inheritance with QObject to your QRunnable (just make sure that QObject is always the first in the list). 要回答第二个问题,您可以使用QObjectQRunnable创建多重继承(只需确保QObject始终是列表中的第一个)。 You can then pass your data around using the signal/slot mechanism exactly the same way you are in your test example. 然后,您可以使用信号/插槽机制完全按照测试示例中的方式传递数据。

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

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

Actually, you could use this structure for your Acquire class as well since your aim is to run code in a different thread, not add extra features to the thread itself. 实际上,您也可以将这种结构用于Acquire类,因为您的目标是在不同的线程中运行代码,而不是向线程本身添加额外的功能。

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

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