Излучение эталона QVector в сигнале Qt приводит к копированию

Я пытаюсь проложить себе путь через создание приложения для общения с линейной камерой. В конечном счете, я хочу передать "блок" (т.е. массив) размером 384x128 unsigned short значения каждые 100 мс от 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> повсюду различные итерации использования ссылок и т. д. Адрес всегда меняется. Поскольку производительность здесь будет важна, я обеспокоен созданием копий QVector при значениях 384*128.

Кроме того, в другом вопросе SO я пытаюсь выяснить, как заставить QRunnable получать данные (все это исключено из этого примера). Но здесь важно отметить, потому что моя идея состоит в том, чтобы заставить QRunnable работать со ссылкой на image_buffer который живет в QThread. Я просто не понял, как это сделать.

Конкретные вопросы:

- Почему, кажется, копия сделана? Это из-за многопоточности?

Я должен явно использовать ссылки (то есть, 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);

    }
}

2 ответа

В этом случае копии будут создаваться как потому, что вы передаете по значению, так и потому, что сигналы через границы потоков помещаются в очередь. Это нормально, потому что неявное разделение означает, что они мелкие копии. Практически нет накладных расходов на копирование, если и оригинал, и копия используются только для чтения.

К сожалению, это не совсем так в вашей программе. Ваш вечный цикл изменит вектор, когда он вернется назад после испускания сигнала. В этом примере он фактически ничего не изменит в векторе, так как вы всегда просто присваиваете 1,2,3,4, но достаточно вызвать неконстантный оператор [], чтобы вызвать глубокое копирование.

Мой вывод таков: вы можете быть синхронными и совместно использовать один и тот же буфер между вашими читателями и писателями, или вы можете быть асинхронными и предоставлять копию вашего буфера записи своим читателям. Вы не можете быть асинхронными и использовать один и тот же буфер между вашими читателями и вашими авторами.

То, как вы справляетесь с этим, кажется подходящим для асинхронной обработки. В зависимости от характеристик вашей генерации и обработки данных, вы можете (или не можете) найти синхронное решение, которое будет лучше. Самый простой способ сделать ваш асинхронный код синхронным - это указать тип соединения как Qt::BlockingQueuedConnection на подключении.

Чтобы ответить на второй вопрос, вы можете создать множественное наследование с QObject на ваш QRunnable (просто убедитесь, что QObject всегда первый в списке). Затем вы можете передавать свои данные, используя механизм сигнал / слот, точно так же, как в своем тестовом примере.

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

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

На самом деле, вы можете использовать эту структуру для вашего Acquire класс, так как ваша цель - запускать код в другом потоке, а не добавлять дополнительные функции в сам поток.

Другие вопросы по тегам