1

I have a object of class MyProcessor, it runs in a thread. It has some state variables (int, bool, double). These variables are used in many methods of class MyProcessor (about 3000->4000 lines of code use them).

In main thread, I want to read value of state variables to enable or disable some actions on GUI. I don't want use mutex because I must add many lock, unlock in class MyProcessor.
Can anybody recommend me another idea (in C++ 11, boost or Qt)?

shang12
  • 423
  • 5
  • 18
  • 3
    `int, bool, double` can all be made atomic. – cmannett85 Aug 24 '15 at 09:58
  • 2
    std::atomic from C++11 might help – Hcorg Aug 24 '15 at 10:02
  • 1
    You could consider having some atomics for the actual sharing - giving them slightly different names - and only updating them periodically, or perhaps as certain operations finish, from the background thread: such an approach avoids the cost of atomic updates every time, and means any existing operations like `a += b; a -= c;` that would temporarily produce a value you wouldn't want the main thread to see won't be visible therefrom. – Tony Delroy Aug 24 '15 at 11:59
  • Using std::atomic is very useful. In gui thread, I can get values with std::atomic::load(). In MyProcessor thread, do I need to call load() to get values ? I hope that I wouldn't have to change anything in MyProcessor code. – shang12 Aug 25 '15 at 02:31

1 Answers1

1

Your MyProcessor should indicate any property changes as they happen. Then it's a trivial matter for the GUI objects to react to the changes and adjust its state:

#include <QtWidgets>

class MyProcessor : public QObject {
  Q_OBJECT
  Q_PROPERTY(int value READ value NOTIFY valueChanged)
  QBasicTimer m_timer;
  int m_value;
  void timerEvent(QTimerEvent * ev) {
    if (ev->timerId() != m_timer.timerId()) return;
    m_value = m_value == 42 ? 0 : 42;
    emit valueChanged(m_value);
  }
public:
  MyProcessor(QObject * parent = 0) : QObject(parent) { m_timer.start(1000, this); }
  Q_SIGNAL void valueChanged(int);
  int value() const { return m_value; }
};

class Gui : public QObject {
  Q_OBJECT
  QLabel m_label;
public:
  Gui(QObject * parent = 0) : QObject(parent) {
    m_label.setMinimumWidth(qApp->fontMetrics().averageCharWidth() * 30);
    m_label.show();
  }
  Q_SLOT void newValue(int value) {
    m_label.setText(value == 42 ? "OK, correct value" : "Oops, wrong value");
  }
};

// We must fix the broken-by-design QThread
struct Thread : public QThread { ~Thread() { quit(); wait(); } };

int main(int argc, char ** argv) {
  QApplication app(argc, argv);
  Gui gui;
  MyProcessor proc;
  Thread procThread;
  proc.moveToThread(&procThread);
  procThread.start();
  QObject::connect(&proc, &MyProcessor::valueChanged, &gui, &Gui::newValue);
  return app.exec();
}
#include "main.moc"

Only if both MyProcessor does not signal property changes, and you cannot modify it to do so, would you poll for up-to-date values. You can either poll periodically, or at predetermined moments - this depends on the design of your system. In the example below, I'll assume that one must poll periodically.

The correct implementation is to break the call up into two parts: a request, and a response handler. Your goal is to invoke some code in another object, and then invoke some code in the source object when the response is available.

You should fully leverage C++11's lambda syntax to keep the code concise and retain the locality needed for a human maintainer to follow what's going on. The value is acquired in the target object, and then processed in the requesting object.

The only changes, compared to the first, preferred solution, are:

  1. MyProcessor does not notify of property changes.
  2. The Gui thread-safely, and in a non-blocking fashion polls the processor for property values.
// USE ONLY IF MyProcessor PROPERTIES HAVE NO CHANGE NOTIFIERS
#include <QtWidgets>

class MyProcessor : public QObject {
  Q_OBJECT
  Q_PROPERTY(int value READ value)
  QBasicTimer m_timer;
  int m_value;
  void timerEvent(QTimerEvent * ev) {
    if (ev->timerId() != m_timer.timerId()) return;
    m_value = m_value == 42 ? 0 : 42;
  }
public:
  MyProcessor(QObject * parent = 0) : QObject(parent) { m_timer.start(1000, this); }
  int value() const { return m_value; }
};

// See http://stackoverflow.com/a/21653558/1329652
template <typename F>
void postTo(QObject * obj, F && fun) {
  if (!obj) return;
  if (obj->thread() != QThread::currentThread()) {
    QObject signalSource;
    QObject::connect(&signalSource, &QObject::destroyed, obj, std::forward<F>(fun));
  } else
    fun();
}

class Gui : public QObject {
  Q_OBJECT
  QBasicTimer m_poll;
  QLabel m_label;
  QPointer<MyProcessor> m_proc;
  void timerEvent(QTimerEvent * ev) {
    if (ev->timerId() != m_poll.timerId()) return;
    postTo(m_proc, [=]{
      // runs in target's thread
      auto value = m_proc->value();
      postTo(this, [=]{ newValue(value); /* runs in our thread */ });
    });
  }
public:
  Gui(MyProcessor * proc, QObject * parent = 0) : QObject(parent), m_proc(proc) {
    m_label.setMinimumWidth(qApp->fontMetrics().averageCharWidth() * 30);
    m_label.show();
    m_poll.start(900, this);
  }
  Q_SLOT void newValue(int value) {
    m_label.setText(value == 42 ? "OK, correct value" : "Oops, wrong value");
  }
};

// We must fix the broken-by-design QThread
struct Thread : public QThread { ~Thread() { quit(); wait(); } };

int main(int argc, char ** argv) {
  QApplication app(argc, argv);
  MyProcessor proc;
  Thread procThread;
  proc.moveToThread(&procThread);
  procThread.start();
  Gui gui(&proc);
  return app.exec();
}
#include "main.moc"

Finally, as a quick hack you could use blocking queued connections to call slots in other threads. This works only if the property read accessors are slots - it is a hack, after all. The GUI thread will block while waiting for the results, but you can safely read some values for debugging purposes that way. If you ship such code to your users, they will hate you.

// DO NOT USE THIS CODE IN PRODUCTION!
// THIS IS A HORRIBLE USABILITY BUG
// ONLY USE FOR DEBUGGING!!
#include <QtWidgets>

class MyProcessor : public QObject {
  Q_OBJECT
  Q_PROPERTY(int value READ value)
  QBasicTimer m_timer;
  int m_value;
  void timerEvent(QTimerEvent * ev) {
    if (ev->timerId() != m_timer.timerId()) return;
    m_value = m_value == 42 ? 0 : 42;
  }
public:
  MyProcessor(QObject * parent = 0) : QObject(parent) { m_timer.start(1000, this); }
  Q_SLOT int value() const { return m_value; }
};

class Gui : public QObject {
  Q_OBJECT
  QBasicTimer m_poll;
  QLabel m_label;
  QPointer<MyProcessor> m_proc;
  void timerEvent(QTimerEvent * ev) {
    if (ev->timerId() != m_poll.timerId()) return;
    newValue(getValue());
  }
public:
  Gui(MyProcessor * proc, QObject * parent = 0) : QObject(parent), m_proc(proc) {
    m_label.setMinimumWidth(qApp->fontMetrics().averageCharWidth() * 30);
    m_label.show();
    m_poll.start(900, this);
  }
  Q_SIGNAL int getValue();
  Q_SLOT void newValue(int value) {
    m_label.setText(value == 42 ? "OK, correct value" : "Oops, wrong value");
  }
};

// We must fix the broken-by-design QThread
struct Thread : public QThread { ~Thread() { quit(); wait(); } };

int main(int argc, char ** argv) {
  QApplication app(argc, argv);
  MyProcessor proc;
  Thread procThread;
  proc.moveToThread(&procThread);
  procThread.start();
  Gui gui(&proc);
  QObject::connect(&gui, &Gui::getValue, &proc, &MyProcessor::value, Qt::BlockingQueuedConnection);
  return app.exec();
}
#include "main.moc"
Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313
  • Thank you for your help ! I am using std::atomic, I will try your code later. – shang12 Aug 25 '15 at 02:44
  • @shang12 `std::atomic` solves only the problem of data consistency, but it doesn't solve the issue of notifications. Once you have the notifications, you don't need to access the compute object's data anymore, since the notification includes the new state of the data item. – Kuba hasn't forgotten Monica Aug 25 '15 at 12:36