首页 > 解决方案 > QtConcurrent:为什么releaseThread和reserveThread会导致死锁?

问题描述

在 Qt 4.7 Reference for 中QThreadPool,我们发现:

void QThreadPool::releaseThread()

释放先前由调用保留的线程reserveThread()

注意:在没有预先保留线程的情况下调用此函数会暂时增加maxThreadCount(). 当一个线程进入睡眠等待更多工作时,这很有用,允许其他线程继续。完成等待时一定要调用reserveThread(),这样线程池才能正确维护activeThreadCount().

另请参阅reserveThread()


void QThreadPool::reserveThread()

保留一个线程,忽略activeThreadCount()maxThreadCount()

完成线程后,调用releaseThread()以允许重用它。

注意:此函数将始终增加活动线程的数量。这意味着通过使用此函数,可以 activeThreadCount()返回一个大于 的值maxThreadCount()

另请参阅releaseThread()

我想用来releaseThread()使使用嵌套并发映射成为可能,但在下面的代码中,它挂在waitForFinished()

#include <QApplication>
#include <QMainWindow>
#include <QtConcurrentMap>
#include <QtConcurrentRun>
#include <QFuture>
#include <QThreadPool>
#include <QtTest/QTest>
#include <QFutureSynchronizer>

struct Task2 { // only calculation
    typedef void result_type;
    void operator()(int count) {
        int k = 0;
        for (int i = 0; i < count * 10; ++i) {
            for (int j = 0; j < count * 10; ++j) {
                k++;
            }
        }
        assert(k >= 0);
    }
};

struct Task1 { // will launch some other concurrent map
    typedef void result_type;
    void operator()(int count) {

        QVector<int> vec;
        for (int i = 0; i < 5; ++i) {
            vec.push_back(i+count);
        }
        Task2 task;

        QFuture<void> f = QtConcurrent::map(vec.begin(), vec.end(), task);
        {
            // with out releaseThread before wait, it will hang directly
            QThreadPool::globalInstance()->releaseThread();
            f.waitForFinished(); // BUG: may hang there
            QThreadPool::globalInstance()->reserveThread();
        }
    }
};


int main() {
    QThreadPool* gtpool = QThreadPool::globalInstance();
    gtpool->setExpiryTimeout(50);
    int count = 0;
    for (;;) {
        QVector<int> vec;
        for (int i = 0; i < 40 ; i++) {
            vec.push_back(i);
        }
        // launch a task with nested map
        Task1 task; // Task1 will have nested concurrent map
        QFuture<void> f = QtConcurrent::map(vec.begin(), vec.end(),task);

        f.waitForFinished(); // BUG: may hang there

        count++;

        // waiting most of thread in thread pool expire
        while (QThreadPool::globalInstance()->activeThreadCount() > 0) {
            QTest::qSleep(50);
        }

        // launch a task only calculation
        Task2 task2;
        QFuture<void> f2 = QtConcurrent::map(vec.begin(), vec.end(), task2);

        f2.waitForFinished(); // BUG: may hang there

        qDebug() << count;
    }
    return 0;
}

此代码不会永远运行;它会在许多循环(1~10000)后挂起,所有线程都在等待条件变量。

我的问题是:

  1. 为什么会挂?
  2. 我可以修复它并保留嵌套的并发映射吗?

开发环境:

Linux版本2.6.32-696.18.7.el6.x86_64;Qt4.7.4;海合会 3.4.5

Windows 7的; Qt4.7.4;明格 4.4.0

标签: c++multithreadingqtqthreadqtconcurrent

解决方案


当您尝试处理 expiryTimeout 时,程序会因为 QThreadPool 中的竞争条件而挂起。下面是详细分析:

QThreadPool 中的问题-源码

当开始一个任务时,QThreadPool 做了一些类似的事情:

QMutexLocker locker(&mutex);

taskQueue.append(task); // Place the task on the task queue
if (waitingThreads > 0) {
   // there are already running idle thread. They are waiting on the 'runnableReady' 
   // QWaitCondition. Wake one up them up.
   waitingThreads--;
   runnableReady.wakeOne();
} else if (runningThreadCount < maxThreadCount) {
   startNewThread(task);
}

线程的主循环如下所示:

void QThreadPoolThread::run()
{
  QMutexLocker locker(&manager->mutex);
  while (true) {
    /* ... */
    if (manager->taskQueue.isEmpty()) {
      // no pending task, wait for one.
      bool expired = !manager->runnableReady.wait(locker.mutex(), 
                                                  manager->expiryTimeout);
      if (expired) {
        manager->runningThreadCount--;
        return;
      } else {
        continue;
      }
    }
    QRunnable *r = manager->taskQueue.takeFirst();
    // run the task
    locker.unlock();
    r->run();
    locker.relock();
  }
}

这个想法是线程将为任务等待给定的秒数,但如果在给定的时间内没有添加任何任务,则线程到期并终止。这里的问题是我们依赖于runnableReady的返回值。如果有一个任务在线程过期的同时被调度,那么线程将看到 false 并过期。但是主线程不会重新启动任何其他线程。这可能会让应用程序挂起,因为任务永远不会运行。

快速的解决方法是使用较长的 expiryTime(默认为 30000)并删除等待线程过期的 while 循环。

下面是修改后的main函数,程序在Windows 7下运行流畅,默认使用4个线程:

int main() {
    QThreadPool* gtpool = QThreadPool::globalInstance();
    //gtpool->setExpiryTimeout(50); <-- don't set the expiry Timeout, use the default one.
    qDebug() << gtpool->maxThreadCount();

    int count = 0;
    for (;;) {

        QVector<int> vec;
        for (int i = 0; i < 40 ; i++) {
            vec.push_back(i);
        }
        // launch a task with nested map
        Task1 task; // Task1 will have nested concurrent map
        QFuture<void> f = QtConcurrent::map(vec.begin(), vec.end(),task);

        f.waitForFinished(); // BUG: may hang there

        count++;

        /*
        // waiting most of thread in thread pool expire
        while (QThreadPool::globalInstance()->activeThreadCount() > 0)
        {
            QTest::qSleep(50);
        }
        */

        // launch a task only calculation
        Task2 task2;
        QFuture<void> f2 = QtConcurrent::map(vec.begin(), vec.end(), task2);

        f2.waitForFinished(); // BUG: may hang there

        qDebug() << count ;
    }
    return 0;
}

推荐阅读