QtConcurrent:为什么releaseThread和reserveThread导致死锁?

时间:2018-12-13 08:09:03

标签: c++ multithreading qt qthread qtconcurrent

QThreadPool的Qt 4.7参考中,我们发现:

  

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. 我可以修复它并保留嵌套的并发映射吗?

dev env:

Linux版本2.6.32-696.18.7.el6.x86_64; Qt4.7.4; GCC 3.4.5

Windows 7; Qt4.7.4; mingw 4.4.0

2 个答案:

答案 0 :(得分:1)

当您尝试处理expiryTimeout时,由于QThreadPool中的竞争条件,程序挂起。这是详细的分析:

QThreadPool中的问题-source

  

启动任务时,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。 如果计划在   线程到期的时间恰好相同,那么线程将看到   假,将过期。但是主线程不会重新启动其他任何线程   线。这可能会使应用程序挂起,因为任务永远不会挂起   运行。

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

这里是修改后的主要功能,程序在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;
}

答案 1 :(得分:1)