即使删除线程,线程数也会增加很多

Posted

技术标签:

【中文标题】即使删除线程,线程数也会增加很多【英文标题】:Thread count increases a lot, even when deleting the threads 【发布时间】:2012-04-17 02:46:45 【问题描述】:

有一个应用程序,其中我有 QOBJects,它们都包含一个 QNetworkAccessManager。我知道建议只对每个应用程序进行一次调用,但由于我同时进行了 6 次以上的调用,所以我需要这样。所以,这就是我启动线程的方式。

FileUploader *fileUploader = new FileUploader(_fileList);
QThread *fileUploaderThread = new QThread();
fileUploader->moveToThread(fileUploaderThread);

// uploader > model
connect(fileUploader, SIGNAL(progressChangedAt(int)), _model, SLOT(reportProgressChanged(int)), Qt::QueuedConnection);
connect(fileUploader, SIGNAL(statusChangedAt(int)), _model, SLOT(reportStatusChanged(int)), Qt::QueuedConnection);
// uploader > its thread
connect(fileUploader, SIGNAL(canceled()), fileUploaderThread, SLOT(quit()), Qt::QueuedConnection);
connect(fileUploader, SIGNAL(finished()), fileUploaderThread, SLOT(quit()), Qt::QueuedConnection);
// uploader > this
connect(fileUploader, SIGNAL(canceled()), this, SLOT(deleteFinishedUploader()), Qt::QueuedConnection);
connect(fileUploader, SIGNAL(finished()), this, SLOT(deleteFinishedUploader()), Qt::QueuedConnection);
connect(fileUploader, SIGNAL(finishedCurrentUpload()), this, SLOT(uploadNextFileOrFinish()), Qt::QueuedConnection);
// thread > this
connect(fileUploaderThread, SIGNAL(finished()), this, SLOT(checkIfAllThreadsAreFinished()), Qt::QueuedConnection);
connect(fileUploaderThread, SIGNAL(finished()), this, SLOT(deleteFinishedThread()), Qt::QueuedConnection);
// this > uploader
connect(this, SIGNAL(cancel()), fileUploader, SLOT(cancel()), Qt::QueuedConnection);

fileUploaderThread->start();
QMetaObject::invokeMethod(fileUploader, "init", Qt::QueuedConnection);
QMetaObject::invokeMethod(fileUploader, "uploadAt", Qt::QueuedConnection, Q_ARG(int, startIndex));

QMutexLocker locker(&_mutex);
_threadCount++;

每个线程都以列表的索引开始,以便他们可以获取需要上传的内容并继续执行大约 5 个步骤(使用 QNetworkAccessManager 调用)。当没有更多项目要上传时,fileUploader 会发出“finished()”信号,它会调用我所做的deleteFinishedThreaddeleteFinishedUploader

QThread *thread = qobject_cast<QThread*>(sender());

if(thread != NULL) thread->deleteLater();

FileUploader *fileUploader = qobject_cast<FileUploader*>(sender());

if(fileUploader != NULL) fileUploader->deleteLater();

这些是假设在完成后删除线程。

问题是每次我启动(例如)3 个线程,每个线程都有 1 个文件要上传和处理,线程数会增加 8-10。这意味着如果我重新启动上传过程几次,线程数将从大约 5 变为 100。

我做错了什么?还是我最大的问题是我使用“Windows 任务管理器”来控制它?我正在处理我删除的 QNAM 的所有回复,所有内容似乎都被删除了,但是当线程数不断增加时,我仍然摸不着头脑......

编辑: 在我的文件上传器中,我在堆栈上创建了一个对象(管理器),它在堆栈上有一个 QNetworkAccessManager。当文件上传器被删除时,它会在管理器上调用“deleteLater()”,但它永远不会被删除。我们试图删除 Manager 并将其设置为 NULL,但这给了我们访问冲突,因为 Manager 还没有完成(QNetwork.dll 报告了这个问题,所以它必须是 QNAM 内部仍在运行的东西)。当我们没有遇到访问冲突时,对象被删除并且线程数恢复正常。 QNAM 中有什么可以阻止我在超出范围时删除它?我应该在堆上创建 QNAM 吗?在这个阶段,即使调用 deleteLater(),也不会调用任何析构函数......

另外,如何减少句柄数?

【问题讨论】:

添加了赏金。你的问题需要一些爱... 哦,非常感谢。你让我开心:) 一条评论:如果您仍在使用它们,请在开始 fileUploaderThread 之前致电 QMetaObject::invokeMethod(。这是确保线程循环开始时首先调用这些槽的最佳方法。 如果我在线程启动之前调用“init”,我所有的堆分配对象都将位于主线程中 no QMetaObject::invokeMethod 会将堆栈“init”放入偶数循环中并首先执行。这就是QApplication a(argc, argv); MainWindow w; w.show(); return a.exec(); 中发生的事情。一旦应用程序启动偶数循环,就会调用 show()。 (参见调用方法文档) 【参考方案1】:

我可能错了,但我认为你的信号有问题:

// uploader > its thread
connect(fileUploader, SIGNAL(canceled()), fileUploaderThread, SLOT(quit()), Qt::QueuedConnection);
connect(fileUploader, SIGNAL(finished()), fileUploaderThread, SLOT(quit()), Qt::QueuedConnection);
// uploader > this
connect(fileUploader, SIGNAL(canceled()), this, SLOT(deleteFinishedUploader()), Qt::QueuedConnection);
connect(fileUploader, SIGNAL(finished()), this, SLOT(deleteFinishedUploader()), Qt::QueuedConnection);

请记住,当多个插槽连接到同一个信号时,它们会按照连接顺序执行。这里,当fileUploader完成时,它会调用finished(),它会先调用线程的quit()方法,然后再调用deleteFinishedUploader()方法。 canceled() 信号也一样。 但是,与此同时,线程已完成,因此无法对 fileUploader 进行任何事件处理(moveToThread(...) 的结果)。 deleteLater() 需要事件处理,因此您的 fileUploader 将永远不会被删除...

我不是 100% 认为以另一种方式安排你的连接会让事情正常进行:deleteLater() 可以被调用,然后线程立即退出,无需事件处理。

解决方案可能是将 fileUploader 重新moveToThread() 到主线程,或者到仍在处理其事件循环的线程。

【讨论】:

【参考方案2】:

不是答案,而是:

    fileUploaderThread->start();
    QMetaObject::invokeMethod(fileUploader, "init", Qt::QueuedConnection);
    QMetaObject::invokeMethod(fileUploader, "uploadAt", Qt::QueuedConnection, Q_ARG(int, startIndex));

意味着您启动偶数循环,然后将要执行的插槽或信号排队。假设(通常)在此线程中还有其他QObject。由于事件循环已经开始,它们可能会执行它们的插槽或信号。如果您希望“init”和“uploadAt”成为事件循环运行时首先调用的方法,则在启动事件循环之前将它们排队(如果线程未启动,它们将永远不会被执行)。

来自QMetaObject::invokeMethod:

如果 type 是 Qt::QueuedConnection,将发送一个 QEvent 并在应用程序进入主事件循环后立即调用该成员。

在这种情况下,事件被发送到线程事件循环。

【讨论】:

哦,好吧。真的很高兴知道谢谢!然而,在这个例子中,我可能只是坚持我得到的,因为上传者将是该线程中唯一的项目:)【参考方案3】:

经过多次“几乎放弃”后,我想出了线程的解决方案。 Synxis 所说的插槽顺序是真的。

但是,我仍然对文件句柄有一些问题,如果有人有比我更好的答案,我很乐意接受。

我将代码更改为:

...
// uploader > this
connect(fileUploader, SIGNAL(canceled()), this, SLOT(deleteFinishedUploader()), Qt::QueuedConnection);
connect(fileUploader, SIGNAL(finished()), this, SLOT(deleteFinishedUploader()), Qt::QueuedConnection);
// uploader > its thread
connect(fileUploader, SIGNAL(destroyed()), fileUploaderThread, SLOT(quit()));

这意味着当对象被删除时线程会停止(quit())。即使文档指出,这实际上也有效:

此信号在对象 obj 被销毁之前立即发出,并且不能被阻塞。

所有对象的子对象在此信号发出后立即销毁。

这意味着这个信号会在任何东西被破坏之前发出(这意味着我会在其中的上传者被删除之前退出线程)?还不够好,这可能是一个更好的方法。但是,atm,每次上传完成并在 20 秒左右后恢复正常时,我的线程数都会下降很多(一些“观察者线程”必须被 Windows 等杀死)。

【讨论】:

插槽的好解决方案。文件句柄如何管理/创建/删除?他们有什么问题? 我不太确定。有一些 c# 的经验以及你当时的做法,但我现在只能看到句柄的数量在增加但没有减少 问题是我不太确定是否在线程内部的对象被破坏之前删除线程.. 如果你的意思是用'deleteLater()'删除,不,因为它需要一个事件循环才能工作。您必须在删除线程之前删除对象或将对象移动到另一个线程。 不太清楚你的意思。它目前可以工作,并且上传器中的所有内容以及它所在的线程都被删除。这可能是因为(上传器的)基类 QOBject 是最后要调用的东西,并且当它发出“destroyed”(这将破坏线程)真的没有其他可以删除的东西了。是的,在 ~QObject 中还有一些可能非常重要的事情要做,所以我将把对象移到主线程。

以上是关于即使删除线程,线程数也会增加很多的主要内容,如果未能解决你的问题,请参考以下文章

ThreadPoolExecutor线程池参数设置技巧

Java线程池的配置

ThreadPoolExecutor线程池参数设置技巧

ThreadPoolExecutor线程池参数设置技巧

ThreadPoolExecutor线程池参数设置技巧

ThreadPoolExecutor线程池参数设置技巧(转)