[英]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
。
要回答第二個問題,您可以使用QObject
向QRunnable
創建多重繼承(只需確保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.