在 Qt 信号中发射 QVector 引用会导致复制 [英] Emitting QVector reference in Qt signal results in copy

查看:113
本文介绍了在 Qt 信号中发射 QVector 引用会导致复制的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在努力构建一个与线扫描相机对话的应用程序.最终,我想每 100 毫秒从 QThread(数据采集)向 QRunnable<传递一个 384x128 unsigned short 值的块"(即数组)/code>(数据处理).这意味着 QRunnable 在下一个块到达之前将有 100 毫秒的时间来处理数据.

I'm trying to slog my way through building an application to talk to a linescan camera. Ultimately, I want to pass a "block" (i.e., array) of 384x128 unsigned short values every 100ms from a QThread (data acquisition) to a QRunnable (data processing). This means the QRunnable will have 100ms to process the data before the next block arrives.

我仍然不确定移动数据的正确方法.现在,我正在使用 QVector.在 Qt4 中,我理解隐式共享意味着如果在信号中发出 QVector,则不会复制 QVector,直到写入对象.但是,在我制作的一个小型测试应用程序中,我不确定我是否完全理解这意味着什么.这是下面提供的 MWE 的输出...

I'm still not sure the right way to move the data around. Right now, I'm using a 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. 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...

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,使用引用的各种迭代等.地址总是在变化.由于性能在这里很重要,我担心在 384*128 值时制作 QVector 的副本.

I am using a "dummy" QVector with four values and tracking the address of the vector as the thread runs. 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. Since performance will be important here, I'm concerned about making copies of the QVector when 384*128 values.

此外,在另一个 SO 问题中,我正在努力弄清楚如何让 QRunnable 接收数据(这个例子中没有).但是,这里要注意这一点很重要,因为我的想法是让 QRunnable 对位于 QThread 中的 image_bufferreference 进行操作.我只是不知道该怎么做.

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). 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. I just haven't figured out how to do that.

具体问题:

-- 为什么看起来是复制的?这是因为多线程吗?

-- Why does it appear a copy is made? Is this because of multithreading?

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

-- Do I have to explicitly use references (i.e., image_buffer&) throughout, thereby removing QVector entirely? Or, should I worry about the copies?

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

-- What is the right way to pass data from QThread to QRunnable for my situation?

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

MWE below to demonstrate the different addresses. Disclaimer: I'm learning C++ as I go. No formal training, just a few good books in front of me.

ma​​in.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

#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);

    }
}

推荐答案

在这种情况下将进行复制,因为您是按值传递的,也因为跨线程边界的信号已排队.不过没关系,因为 implicit-sharing 表示它们是浅拷贝.如果原件和副本都只用于阅读,那么复制几乎没有开销.

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.

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

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.

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

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.

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

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.

这篇关于在 Qt 信号中发射 QVector 引用会导致复制的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆