【问题标题】:How to get results from a compute thread to the GUI thread - do I need QSharedMemory?如何从计算线程获取结果到 GUI 线程 - 我需要 QSharedMemory 吗?
【发布时间】:2015-08-24 09:16:48
【问题描述】:

我需要两个线程(主线程 (GUI) 和计算线程)之间的共享接口。在启动期间,主线程将所有信号存储在QHash(<QString Signalname, double value>) 中。第二个线程在每个计算步骤中写入键/值。我应该将它们存储在QSharedMemory 中吗?

我的应用程序有两个线程。一个 GUI 线程和一个计算线程。用户可以通过 GUI 以快速模式或实时模式启动模拟。计算随着时间的推移演变模拟模型。它们足够快,可以比实时更快地执行。如果您插入延迟,则可以将它们保持在实时速度,就像您想要的那样,例如在游戏或动画中。

计算线程应该以四种模式之一执行计算:

  1. 快速模式:计算完成时没有延迟,模型的演化速度比实时更快。这是在 Thread run with while (...) { ... run_ComputeFunction()... }

  2. 中实现的
  3. Real-Time 模式:这是在带有 QTimer Timeout 和 slot run_ComputeFunction() 的线程运行中实现的。

  4. 单步模式:用户通过 GUI 启动每个时间步。这类似于调试器中的单步执行。这是在 Thread run with start Thread than run_ComputeFunction than wait 条件下实现的。

  5. 停止模式:停止计算。

【问题讨论】:

  • 这是一个复杂的问题。简而言之:您需要使用 write 并使用您自己的分配器(用于 QHash、QString 和 double),这将与共享内存缓冲区一起使用。但我建议你看看 boost 进程间容器。
  • 在您的特定情况下可能不实用,但如果是这样的话,如果您可以计算出信号的顺序,例如如果所有访问线程/进程都将看到同一组Signalname 值,他们可以std::sort 它们并使用std::binary_search 获取数组索引 - 然后将共享内存视为doubles 的数组。跨度>
  • 真的需要 SharedMemory 吗? SharedMemory 用于进程之间的通信。对于同一进程内的线程之间的通信,只要所有访问都正确同步,数据就可以更容易地作为全局变量或堆共享。

标签: c++ qt


【解决方案1】:

TL;DR:你不知道。

我需要两个线程(主线程 (GUI) 和计算线程)之间的共享接口。 (强调我的)

QSharedMemory 仅对多处理有意义,而不是多线程。线程的整体理念是它们就像进程一样,但共享它们所在进程的所有内存。

因此,您的所有线程都可以看到所有您的进程可用的内存,而您根本不需要做任何事情来共享它。

你需要弄清楚计算线程是否应该是:

  1. 只更新与主线程共享的数据结构,

  2. 通过提供发生变化的键值对通知主线程其状态发生变化,

  3. 两者。

更新数据结构的问题在于,除非您提供更改通知,否则主线程将别无选择,只能轮询结构以进行更改。所以现在你在主线程上运行一些代码,这些代码并没有做太多,但运行在一个计时器上并强制 CPU 唤醒,让你的移动和虚拟化用户讨厌你。不好看。

由于对共享数据结构的任何访问都必须受到互斥体的保护,这样读者就不会在写入者更新值时读取胡言乱语,因此您可以自行决定:

  1. 在计算线程中只保留一份数据结构的副本,并在互斥锁保护下访问它。主线程和计算线程将争夺该互斥体,性能会受到影响。

  2. 保留数据结构的两份副本 - 一份在计算线程中,另一份在希望跟踪计算进度的任何线程中。

  3. 保留一份无锁数据结构的副本。

下面,我将演示如何在计算线程和主线程中保持单独的数据结构。我还利用模型视图和标准模型为计算引擎提供 UI。

计算机的实现分为抽象基类和特定于您的计算问题的具体实现。一个具体的类需要做的就是实现两种方法:一种是计算一个模拟时间步长,返回它的长度,另一种是通知它的用户数据的变化。

Computer 对象提供对其数据的随机更新,“模拟”随机持续时间的时间步长。它通过休眠随机选择的时间步长的 1/4 来阻塞随机的时间量。因此,它的行为就好像它可以比实时快 4 倍地计算数据。

AbstractComputer 采用这个简单的功能并在此基础上提供四种操作模式:单步、实时和快速。

computeChunk在一个块中执行的计算量,在模拟的时间流逝方面,设置为m_notifyPeriod,这里设置为20ms。

在除Stop 之外的所有操作模式中,在将控制权返回给事件循环以保持计算线程响应之前,总是至少计算单个块。单个计时器用于将控制从事件循环返回给计算机并安排未来的计算。在实时模式下,如果计算提前于实时时钟,则下一次计算将安排在适当的时间,以便实时运行。在快速模式下,计时器设置为零超时,立即从事件循环返回控制以执行另一个计算块。这具有非常低的开销。

抽象计算机跟踪累积的模拟时间步长 (simTime)。

计算机对象在自己的线程中运行,并为其数据提供定期、随机生成的更新。通过通知信号指示对数据的更改。更新后的数据被插入到 UI 对象中的标准模型中。连接两个对象的信号槽连接属于排队类型 - newValue 槽调用发生在主线程中。

下拉组合具有焦点,因此您只需按住 (向下箭头键)即可单步执行。

请注意,AbstractComputer 的实现是以这样一种方式完成的,即不会阻塞比m_notifyPeriod 或一个模拟步骤(一次调用compute)中的较大者更长的时间。在单核机器上,将Computer 实例移动到另一个线程实际上会降低性能! main 是为了考虑到这一点而实施的。

最后,作为性能优化,您应该在(可选排序的)字符串表中使用string interning,并使用该表的索引(也称为原子)作为参数键,而不是原始字符串。

#include <QtWidgets>
#include <random>

class AbstractComputer : public QObject {
   Q_OBJECT
   Q_PROPERTY (Mode mode READ mode WRITE setMode NOTIFY modeChanged)
   Q_PROPERTY (double simTime READ simTime WRITE setSimTime
               RESET resetSimTime NOTIFY simTimeChanged)
public:
   enum Mode { Stop, Step, RealTime, Fast };
protected:
   typedef double Time; ///< units of seconds

   /// Performs one computation step and returns the amount of time the simulation has
   /// been advanced by. The computation updates one or more parameters in the map, but
   /// doesn't signal the updates. The changed parameters are kept in set.
   /// This method can change m_mode to Stop to direct the calling code to stop/pause
   /// the simulation.
   virtual Time compute() = 0;

   /// Notifies of accumulated changes and clears the update set.
   virtual void notify() = 0 ;
private:
   Mode m_mode, m_prevMode;
   QBasicTimer m_timer;
   QElapsedTimer m_timeBase;
   qint64 m_lastNotification; ///< Last m_timeBase at which notification was issued.
   Time m_notifyPeriod; ///< Real time period to issue data change notifications at.
   Time m_modeSimTime;  ///< Simulation time accumulated in current mode.
   Time m_simTime;      ///< Total simulation time.

   /// Computes a chunk of work that amounts to m_notifyPeriod in simulated time
   void computeChunk() {
      Time t = 0;
      do
         t += compute();
      while (m_mode != Stop && t < m_notifyPeriod);
      m_modeSimTime += t;
      m_simTime += t;
   }

   /// Runs computations according to the selected mode. In RealTime and Fast modes,
   /// the notifications are issued at least every m_notifyPeriod.
   void timerEvent(QTimerEvent * ev) {
      if (ev->timerId() != m_timer.timerId()) return;
      const Time startSimTime = m_simTime;
      const Mode startMode = m_mode;
      switch (m_mode) {
      case Step:
         m_simTime += compute();
         m_timer.stop();
         m_mode = Stop;
         break;
      case Stop:
         m_timer.stop();
         break;
      case RealTime:
         if (m_prevMode != RealTime) {
            m_modeSimTime = 0.0;
            m_timeBase.start();
         }
         computeChunk();
         if (m_mode == RealTime) {
            int ahead = round(m_modeSimTime * 1000.0 - m_timeBase.elapsed());
            if (ahead < 0) ahead = 0;
            m_timer.start(ahead, Qt::PreciseTimer, this);
         }
         break;
      case Fast:
         if (m_prevMode != Fast) {
            m_timeBase.start();
            m_lastNotification = 0;
         }
         do
            computeChunk();
         while (m_mode == Fast
                && ((m_timeBase.elapsed() - m_lastNotification) < m_notifyPeriod*1000.0));
         m_lastNotification = m_timeBase.elapsed();
         break;
      }
      notify();
      if (startSimTime != m_simTime) emit simTimeChanged(m_simTime);
      if (m_prevMode != m_mode || startMode != m_mode) emit modeChanged(m_mode);
      m_prevMode = m_mode;
   }
public:
   AbstractComputer(QObject * parent = 0) :
      QObject(parent), m_mode(Stop), m_prevMode(Stop), m_notifyPeriod(0.02) /* 50 Hz */,
      m_simTime(0.0)
   {}
   Q_SIGNAL void modeChanged(AbstractComputer::Mode mode); // fully qualified type is required by moc
   Q_SIGNAL void simTimeChanged(double);
   Q_SLOT void setMode(AbstractComputer::Mode mode) { // fully qualified type is required by moc
      if (m_mode == mode) return;
      m_mode = mode;
      if (m_mode != Stop) m_timer.start(0, this); else m_timer.stop();
   }
   Q_SLOT void stop() { setMode(Stop); }
   Mode mode() const { return m_mode; }
   double simTime() const { return m_simTime; }
   void setSimTime(double t) { if (m_simTime != t) { m_simTime = t; emit simTimeChanged(t); } }
   void resetSimTime() { setSimTime(0.0); }
};
Q_DECLARE_METATYPE(AbstractComputer::Mode)

class Computer : public AbstractComputer {
   Q_OBJECT
public:
   typedef QHash<QString, double> Map;
private:
   typedef QSet<QString> Set;
   std::default_random_engine m_eng;
   Map m_data;
   Set m_updates;

   Time compute() Q_DECL_OVERRIDE {
      // Update one randomly selected parameter.
      auto n = std::uniform_int_distribution<int>(0, m_data.size()-1)(m_eng);
      auto it = m_data.begin();
      std::advance(it, n);
      auto val = std::normal_distribution<double>()(m_eng);
      *it = val;
      m_updates.insert(it.key());
      float tau = std::uniform_real_distribution<float>(0.001, 0.1)(m_eng);
      // Pretend that we run ~4x faster than real time
      QThread::usleep(tau*1E6/4.0);
      return tau;
   }
   void notify() Q_DECL_OVERRIDE {
      for (auto param : m_updates)
         emit valueChanged(param, m_data[param]);
      m_updates.clear();
   }
public:
   Computer(const Map & data, QObject * parent = 0) :
      AbstractComputer(parent), m_data(data) {}
   Map data() const { return m_data; }
   Q_SIGNAL void valueChanged(const QString & key, double val);
};

class UI : public QWidget {
   Q_OBJECT
   QHash<QString, int> m_row;
   QStandardItemModel m_model;
   QFormLayout m_layout { this };
   QTableView m_view;
   QComboBox m_mode;
public:
   UI(const Computer * computer, QWidget * parent = 0) :
      QWidget(parent),
      m_model(computer->data().size() + 1, 2, this)
   {
      auto data = computer->data();
      m_mode.addItem("Stop", Computer::Stop);
      m_mode.addItem("Step", Computer::Step);
      m_mode.addItem("Real Time", Computer::RealTime);
      m_mode.addItem("Fast", Computer::Fast);
      m_mode.setFocusPolicy(Qt::StrongFocus);
      m_view.setFocusPolicy(Qt::NoFocus);
      m_layout.addRow(&m_view);
      m_layout.addRow("Sim Mode", &m_mode);
      m_model.setItem(0, 0, new QStandardItem("Sim Time [s]"));
      m_model.setItem(0, 1, new QStandardItem);
      int row = 1;
      for (auto it = data.begin(); it != data.end(); ++it) {
         m_model.setItem(row, 0, new QStandardItem(it.key()));
         m_model.setItem(row, 1, new QStandardItem(QString::number(it.value())));
         m_row[it.key()] = row++;
      }
      newMode(computer->mode());
      newSimTime(computer->simTime());
      m_view.setModel(&m_model);
      connect(&m_mode, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
              [this](int i){
         emit modeChanged((AbstractComputer::Mode)m_mode.itemData(i).toInt());
      });
   }
   Q_SIGNAL void modeChanged(AbstractComputer::Mode);
   Q_SLOT void newValue(const QString & key, double val) {
      m_model.item(m_row[key], 1)->setText(QString::number(val));
   }
   Q_SLOT void newSimTime(double t) {
      m_model.item(0, 1)->setText(QString::number(t));
   }
   Q_SLOT void newMode(AbstractComputer::Mode mode) {
      m_mode.setCurrentIndex(m_mode.findData(mode));
   }
};

struct Thread : public QThread { ~Thread() { quit(); wait(); } };

int main(int argc, char *argv[])
{
   QApplication a(argc, argv);
   qRegisterMetaType<AbstractComputer::Mode>();
   Computer::Map init;
   init.insert("Foo", 1);
   init.insert("Bar", 2);
   init.insert("Baz", 3);
   Computer computer(init);
   QScopedPointer<Thread> thread;
   UI ui(&computer);
   QObject::connect(&computer, &Computer::valueChanged, &ui, &UI::newValue);
   QObject::connect(&computer, &Computer::simTimeChanged, &ui, &UI::newSimTime);
   QObject::connect(&computer, &Computer::modeChanged, &ui, &UI::newMode);
   QObject::connect(&ui, &UI::modeChanged, &computer, &Computer::setMode);
   int threadCount = Thread::idealThreadCount();
   if (threadCount == -1 || threadCount > 1) { // Assume a multicore machine
      thread.reset(new Thread);
      computer.moveToThread(thread.data());
      thread->start();
      // Prevent the bogus "QBasicTimer::stop: Failed." warnings.
      QObject::connect(thread.data(), &QThread::finished, &computer, &Computer::stop);
   }
   ui.show();
   return a.exec();
}

#include "main.moc"

我的“随机词”词典确实包含三个项目,非常感谢:)

【讨论】:

    【解决方案2】:

    以防万一您还没有遇到它,Qt 提供了一个 QSharedMemory 示例,可以帮助您朝着正确的方向开始:http://doc.qt.io/qt-5/qtcore-ipc-sharedmemory-example.html

    QThread 文档也会有所帮助:http://doc.qt.io/qt-5/qthread.html

    如果您仍然卡住,您可以随时使用最少的代码示例更新您的问题。

    【讨论】:

      猜你喜欢
      • 2019-03-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多