启动多个线程并重新启动它们

时间:2016-06-23 19:50:42

标签: c++ multithreading qt signals-slots

我正在尝试编写一个我创建x个工作线程的系统。这些线程将在不同的时间完成他们的工作。当他们中的任何一个完成他们的工作时,我将检查他们的输出并再次重新启动它们(保持x的运行线程数)。我会为一些无关紧要的迭代做这件事。因此,基本上控制器线程将启动x个线程,并在完成工作时重新启动它们,直到达到一定数量的迭代。

附加说明#1:当我说重启时,等到当前退出/中止并被销毁并创建一个新的完全没问题。它不必“重启”同一个线程。我最感兴趣的是以干净的异步方式做到这一点。

注意:我不是在寻找任何特定的代码,而是一些可能的伪代码和一个利用插槽和信号的设计模式。

我知道qt线程并且已经完成了它们。我熟悉一些示例,其中您启动了大量的线程并等待所有线程完成使用yield,然后等待。我正在寻找一种干净的方法来实现我在第一段中使用信号和插槽所描述的内容。

2 个答案:

答案 0 :(得分:2)

这是QtConcurrent::run()QThreadPool::start()的用途。 Concurrent框架在内部使用一个线程池,因此它们非常相同:前者是后者的便利包装器。默认线程池最好留给短期运行的任务;要运行长任务,请使用您自己的线程池。你将它作为第一个参数传递给QtConcurrent::run()

QThreadPool维护一个工作项队列,将它们分派给线程,并动态创建和销毁工作线程。这是一个很棒的课程,你不需要重新实现自己。

如果您没有太多的工作单元并且可以预先提供它们,只需使用QtConcurrent::run()QThreadPool::start()将它们排队等候。它们可以从辅助对象发出信号,以便在每个对象完成时通知您。

如果工作单元太昂贵而无法一次创建所有工作单元,则必须在线程池之上实现通知工作队列。

工作单元需要通知队列及其用户已完成。这可以通过例如完成。通过重新实现QRunnable作为WorkUnit的基础,将工作转发到抽象方法,并在抽象方法完成时通知队列。同样的方法适用于QtConcurrent::run,但不是重新实现QRunnable::run而是实现仿函数operator()()

队列将为每个完成的工作单元发出workUnitDone信号。在收到信号时,用户应该用一项工作重新填充队列(如果没有更多的工作,则为没有工作)。

为方便起见,队列可以通过发出workUnitDone(nullptr)来请求许多初始工作项。如果每次前一个项目完成后只补充一个项目,队列将保持初始工作项目数。

如果这些项目需要花费很短的时间来处理,那么你应该拥有比线程数更多的可用项,这样就没有线程会在没有工作的情况下空闲。对于大多数需要很长时间(几十毫秒或更长)的项目,只需要QThread::idealThreadCount的1.5-2倍即可。

添加到队列中的工作单位可以是WorkUnit或仿函数的实例。

// https://github.com/KubaO/stackoverflown/tree/master/questions/notified-workqueue-38000605
#include <QtCore>
#include <type_traits>

class WorkUnit;
class WorkQueue : public QObject {
   Q_OBJECT
   friend class WorkUnit;
   QThreadPool m_pool{this};
   union alignas(64) { // keep it in its own cache line
      QAtomicInt queuedUnits{0};
      char filler[64];
   } d;
   void isDone(WorkUnit * unit) {
      auto queued = d.queuedUnits.deref();
      emit workUnitDone(unit);
      if (!queued) emit finished();
   }
public:
   explicit WorkQueue(int initialUnits = 0) {
      if (initialUnits)
         QTimer::singleShot(0, [=]{
            for (int i = 0; i < initialUnits; ++i)
               emit workUnitDone(nullptr);
         });
   }
   Q_SLOT void addWork(WorkUnit * unit);
   template <typename F> void addFunctor(F && functor);
   Q_SIGNAL void workUnitDone(WorkUnit *);
   Q_SIGNAL void finished();
};

class WorkUnit : public QRunnable {
   friend class WorkQueue;
   WorkQueue * m_queue { nullptr };
   void run() override {
      work();
      m_queue->isDone(this);
   }
protected:
   virtual void work() = 0;
};

template <typename F>
class FunctorUnit : public WorkUnit, private F {
   void work() override { (*this)(); }
public:
   FunctorUnit(F && f) : F(std::move(f)) {}
};

void WorkQueue::addWork(WorkUnit *unit) {
   d.queuedUnits.ref();
   unit->m_queue = this;
   m_pool.start(unit);
}

template <typename F> void WorkQueue::addFunctor(F && functor) {
   addWork(new FunctorUnit<typename std::decay<F>::type>{std::forward<F>(functor)});
}

为了展示一些东西,让我们在1us和1s之间的随机时间内做50个单位的“工作”。我们将一半的单位作为SleepyWork个实例传递,另一半作为lambdas传递。

#include <random>

struct SleepyWork : WorkUnit {
   int usecs;
   SleepyWork(int usecs) : usecs(usecs) {}
   void work() override {
      QThread::usleep(usecs);
      qDebug() << "slept" << usecs;
   }
};

int main(int argc, char ** argv) {
   QCoreApplication app{argc, argv};
   std::random_device dev;
   std::default_random_engine eng{dev()};
   std::uniform_int_distribution<int> dist{1, 1000000};
   auto rand_usecs = [&]{ return dist(eng); };

   int workUnits = 50;
   WorkQueue queue{2*QThread::idealThreadCount()};
   QObject::connect(&queue, &WorkQueue::workUnitDone, [&]{
      if (workUnits) {
         if (workUnits % 2) {
            auto us = dist(eng);
            queue.addFunctor([us]{
               QThread::usleep(us);
               qDebug() << "slept" << us;
            });
         } else
            queue.addWork(new SleepyWork{rand_usecs()});
         --workUnits;
      }
   });
   QObject::connect(&queue, &WorkQueue::finished, [&]{
      if (workUnits == 0) app.quit();
   });

   return app.exec();
}

#include "main.moc"

示例结束。

答案 1 :(得分:0)

每个帖子都有以下内容:

  • 布尔出口
    • 在启动线程之前,将其设置为false
    • 主线程只应将其设置为true。 Order将标志设置为true,加入线程
    • 工作线程只应将其设置为false。订单退出循环,进行任何后处理,将其设置为false,返回。
  • 信号量(不需要反击,只需发布​​/停止,如在pthreads中)
    • 主线程将发布
    • 工作线程将在while( !exit )中运行,循环中的第一个命令是等待信号量。
  • bool threadWorking
    • 在启动线程之前,将其设置为false
    • 主线程只应将其设置为true。顺序是:准备好数据,将标志设置为true,发布信号量。
    • 工作线程只应将其设置为false。 Orther是:准备好答案,通知主线程,将标志设置为false。

这样你可以重复使用该线程。