c++ - 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)后挂起,所有线程都在等待条件变量。
我的问题是:
- 为什么会挂?
- 我可以修复它并保留嵌套的并发映射吗?
开发环境:
Linux版本2.6.32-696.18.7.el6.x86_64;Qt4.7.4;海合会 3.4.5
Windows 7的; Qt4.7.4;明格 4.4.0
解决方案
当您尝试处理 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;
}
推荐阅读
- python - 在 Python 中使用 Mongoengine 中的嵌套/嵌入文档
- javascript - 使用 AJAX、JavaScript 调用 python flask 函数并返回 JavaScript
- python - 使用 BeautifulSoup 进行网页抓取时,我可以接受或忽略 Google 隐私声明吗?
- amazon-dynamodb - 有条件地从列表属性中删除项目
- java - Java - 函数可以返回与输入参数之一相同的类型吗?
- r - R警告:在公式中产生了NaN
- android - android paging 3 line after adapter.submitData 没有被执行
- ssl - ServerKeyExchange 后 TLS 握手中的解密错误
- http - Erlang httpc Web 服务器返回 {error, socket_closed_remotely}
- javascript - Angular js图像上传更改浏览按钮文本