簡體   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