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

3
在Qt 4.7参考文献中的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; GCC 3.4.5

Windows 7; Qt4.7.4; mingw 4.4.0


你的程序在Linux上运行顺畅,所以如果在Windows上出现挂起,那么并发实现方面存在差异。 - tunglt
@tunglt 它会导致Windows和Linux都挂起,我已经更新了环境信息,谢谢! - zuoheng.deng
2个回答


1
程序因QThreadPool中的竞争条件而挂起,当您尝试处理expiryTimeout时会发生这种情况。以下是详细的分析:
在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);
}

这个线程的主循环看起来像这样:

And the the thread's main loop looks like this:

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循环。
以下是修改后的主要函数,在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;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接