2
votes

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.

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}

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.

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.

The specific questions:

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

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

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

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.

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
Your debug code is incorrect. &display_buffer returns the pointer to the object itself. It's not surprising that it will be different in your case. If you want a pointer to the QVariant's internal array, use display_buffer.constData().Pavel Strakhov
@PavelStrakhov That was exactly it. The addresses now match in my debug output... in the QThread, the GUI Thread, and in the QRunnable. Excellent. Acquire thread: block acquired: 0x105501010 || GUI thread: received signal: 0x105501010 || Algorithm thread: 0x105501010ph0t0n

2 Answers

3
votes

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.

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.

0
votes

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). 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.