QRunnable - 如何在没有比赛的情况下实现WaitForFinished?

时间:2014-03-16 15:10:30

标签: c++ multithreading qt

我在Qt中有一些I / O代码被移动到QRunnable以避免阻塞UI线程。这将回调一些排队的插槽以指示其进度。

但是我最近看到了一些问题,当QRunnable的所有者被销毁时,它无法删除runnable直到它完成(否则在工作线程仍在使用它时崩溃)。为了解决这个问题,我想在运行中添加一个“WaitForFinished”,它将在dtor中调用它,这样我就可以删除QRunnable并知道它会阻塞直到它在dtor之前完成退出。

我认为这应该很简单,因为我需要做的就是在QMutex实现中使用QRunnable::run,然后在WaitForFinished中获取该互斥量。然而,这有一个竞争条件,我不知道如何解决。

请考虑以下事项:

  1. QRunnable排队等待执行。
  2. QRunnable开始执行,但尚未调用QRunnable::run
  3. 主线程删除在{d}中调用QRunnable的{​​{1}}并在WaitForFinished之前获取QMutex - 因此我们假设它未运行或已完成运行并销毁QRunnable::run,但它还没有开始!

3 个答案:

答案 0 :(得分:1)

构建runnable时应获取互斥锁,并在run()清空时释放互斥锁。这必须包括run()根本不返回时 - 抛出的异常返回!在两种情况下,您不能随意删除runnable:

  1. 一旦提交给线程池,就在它完成之前,

  2. 如果设置了autoDelete。

    QRunnable::run()在线程池的实现中返回后,如果autoDelete的缓存值为真,则runnable中的内部引用计数器将递减。这会导致悬空指针取消引用。

  3. 以下是这个想法的经过测试的实现。

    输出结果为:

    ~Incrementer 
    1 
    ~Incrementer 
    2 
    
    #include <QThreadPool>
    #include <QRunnable>
    #include <QMutex>
    #include <QDebug>
    
    // This cannot be a class that can be derived from, since the destructor
    // will be run *after* the derived class's destructor - thus it would not
    // protect the derived class's members from premature destruction.
    class RunnableWrapper Q_DECL_FINAL : public QRunnable {
       Q_DISABLE_COPY(RunnableWrapper)
       class LockerUnlocker {
          Q_DISABLE_COPY(LockerUnlocker)
          QMutexLocker * m_mutexLocker;
       public:
          explicit LockerUnlocker(QMutexLocker * m) : m_mutexLocker(m) {}
          ~LockerUnlocker() { m_mutexLocker->unlock(); }
       };
       QRunnable * m_wrapped;
       QMutex m_mutex;
       QMutexLocker m_lock;
       void run() Q_DECL_FINAL {
          LockerUnlocker unlocker(&m_lock);
          m_wrapped->run();
       }
    public:
       RunnableWrapper(QRunnable * r) : m_wrapped(r), m_lock(&m_mutex) {
          setAutoDelete(false);
       }
       void wait() { QMutexLocker lock(&m_mutex); }
       ~RunnableWrapper() {
          wait();
          if (m_wrapped->autoDelete()) delete m_wrapped;
       }
    };
    
    class Incrementer : public QRunnable {
       int * m_val;
       void run() Q_DECL_OVERRIDE { ++ *m_val; }
    public:
       explicit Incrementer(int * val) : m_val(val) {}
       ~Incrementer() { qDebug() << __FUNCTION__; }
    };
    
    int main() {
       QThreadPool pool;
       int i = 0;
       {
          // Use with stack allocated runnable
          Incrementer inc(&i);
          inc.setAutoDelete(false); // Required without a wrapper as well!
          RunnableWrapper wrap(&inc);
          pool.start(&wrap);
       }
       qDebug() << i;
       {
          // Use with heap allocated runnable
          Incrementer * inc = new Incrementer(&i);
          RunnableWrapper wrap(inc);
          pool.start(&wrap);
       }
       qDebug() << i;
       return 0;
    }
    

答案 1 :(得分:1)

首先,没有一种尺寸适合所有解决方案。首选方法取决于任务细节。虽然我已经提出了一些问题,但仍然存在很多模棱两可的问题。但是,我将描述我能想到的最简单的方法。

  1. 启用Runnables自动删除
  2. 创建一些startedJobsCounter并在每次在所有者对象中启动新作业时增加它。
  3. 创建信号量finishedJobsSem,当作业完成时,该作业将被释放。
  4. 所有Runnables的waitForFinished看起来像

    finishedJobsSem.aquire(startedJobsCounter);

  5. 您可以在runnable析构函数中释放finishedJobsSem

  6. startedJobsCounter读写之间没有竞争,因为它们都是在所有者的对象线程中执行的。

    由于所有作业都将在所有者对象被销毁之前完成,因此使用form runnable无法使finishedJobsSem无效。

答案 2 :(得分:1)

如果其他人对runnables有这个问题,这就是我最终解决它的方式。我的用例基本上是我想在另一个线程中执行对象的“run”方法,并知道它何时完成/能够等待它。

我发现QtConcurrent实际上比QRunnable更好。

class MyClass
{
public:
 void run() { }
};

void someFunc()
{
  MyClass instance;

  // Async execute "run" on "instance", returns a future
  auto future = QtConcurrent::run( std::bind(&MyClass::run,instance) );

  // Will emit "finished" signal when done - should keep this and the future in scope of course!
  QFutureWatcher watcher;
  watcher.setFuture( future );

  // Blocks until MyClass::run() returns
  future.waitForFinished();
}