BUG:由Qt::BlockingQueuedConnection引起的关闭Qt主页面而后台仍有进程残留
1、错误代码示例
首先我们看下下面的代码,可以思考一下代码的错误之处
/** BlockingQueueDeadLock.h **/
#pragma once#include <QtWidgets/QMainWindow>
#include "ui_BlockingQueueDeadLock.h"
#include <thread>class BlockingQueueDeadLock : public QMainWindow
{Q_OBJECTpublic:BlockingQueueDeadLock(QWidget *parent = nullptr);~BlockingQueueDeadLock();public slots:void RecvBlockingQueueSignal();
signals:void SendBlockingQueueSignal();private:void startLoopTest();void stopLoopTest();void RunInThread();private:Ui::BlockingQueueDeadLockClass ui;std::thread loopTest;bool m_StopFlag;
};/** BlockingQueueDeadLock.cpp **/
#include "BlockingQueueDeadLock.h"
#include <qdebug.h>BlockingQueueDeadLock::BlockingQueueDeadLock(QWidget *parent): QMainWindow(parent), m_StopFlag(false)
{ui.setupUi(this);startLoopTest();connect(this, &BlockingQueueDeadLock::SendBlockingQueueSignal,this, &BlockingQueueDeadLock::RecvBlockingQueueSignal, Qt::BlockingQueuedConnection);
}BlockingQueueDeadLock::~BlockingQueueDeadLock()
{stopLoopTest();
}void BlockingQueueDeadLock::RunInThread()
{qDebug("%1", std::this_thread::get_id());while (!m_StopFlag) {std::this_thread::sleep_for(std::chrono::microseconds(10));qDebug("signal thread: %1", std::this_thread::get_id());emit SendBlockingQueueSignal();}
}void BlockingQueueDeadLock::RecvBlockingQueueSignal()
{qDebug("slot thread: %1", std::this_thread::get_id());
}void BlockingQueueDeadLock::startLoopTest()
{m_StopFlag = false;loopTest = std::thread(&BlockingQueueDeadLock::RunInThread, this);
}void BlockingQueueDeadLock::stopLoopTest()
{m_StopFlag = true;if (loopTest.joinable()) {loopTest.join();}
}
上面短短几十行代码竟会导致当我关闭Qt主页面时,后台的进程并没有完全退出。
2、原因分析
先使用转储工具获取当前后台进程的堆栈信息;右击后台进程->创建转储文件
这时会获得一个DMP文件,通过windbg分析该DMP文件,如下图所示
我们可以清晰的看到后台进程一直在等待join函数的退出;阅读源码分析join最终调用的时_Thrd_join这个接口,该接口是阻塞的,需要等待线程的主函数运行结束后才会返回。
也就是说我们RunInThread线程主函数迟迟没有结束。
BlockingQueueDeadLock::~BlockingQueueDeadLock()
{stopLoopTest();
}void BlockingQueueDeadLock::RunInThread()
{qDebug("%1", std::this_thread::get_id());while (!m_StopFlag) {std::this_thread::sleep_for(std::chrono::microseconds(10));qDebug("signal thread: %1", std::this_thread::get_id());emit SendBlockingQueueSignal();}
}
void BlockingQueueDeadLock::stopLoopTest()
{m_StopFlag = true;if (loopTest.joinable()) {loopTest.join();}
}
线程的主函数就是一个while循环,在我们BlockingQueueDeadLock析构的时候会将标识符m_StopFlag置为true;按道理讲该while循环应该很快的结束并返回。
但是!但是!但是!我们是否忽略了emit这个信号发射的是如何connect的呢?
connect(this, &BlockingQueueDeadLock::SendBlockingQueueSignal,this, &BlockingQueueDeadLock::RecvBlockingQueueSignal, Qt::BlockingQueuedConnection);
非常非常奇怪的是为什么connect最后一个参数是Qt::BlockingQueuedConnection呢?我看到这个代码也会很奇怪,可能之前的开发者认为发送信号和接受信号的slot不在同一个线程吧!!
3、解决方案
奇怪的地方必有妖,没错就是Qt::BlockingQueuedConnection这里有问题。
先看Qt官方文档的解释:
非常明确的指出了发射的信号与接收的槽不能在同一个线程里,否者会导致应用死锁。
想要了解Qt BlockingQueuedConnection源码的同学可以看下面的文章:
14.QueuedConnection和BlockingQueuedConnection连接方式源码分析_Master Cui的博客-CSDN博客
明确的指出了使用BlockingQueuedConnection同一个线程时死锁的原因。
因此,我们只需要将Qt::BlockingQueuedConnection最后的参数去除,使用默认参数即可。
这就是我发现的Qt主界面关闭,而后台进程未释放的问题;还是由于后台进程中的某个线程没有释放,而该线程没有结束又是由于Qt::BlockingQueuedConnection死锁导致的。
4、总结
当前Qt主界面关闭,而后台进程未释放的原因有很多。这里只是展示了其中一个原因:后台进程中有线程未及时释放导致的,也为遇到同样问题的你提供一个思路。