由Qt::BlockingQueuedConnection引起的关闭Qt主页面而后台仍有进程残留

BUG:由Qt::BlockingQueuedConnection引起的关闭Qt主页面而后台仍有进程残留

1、错误代码示例

首先我们看下下面的代码,可以思考一下代码的错误之处

/** BlockingQueueDeadLock.h **/
#pragma once

#include <QtWidgets/QMainWindow>
#include "ui_BlockingQueueDeadLock.h"
#include <thread>

class BlockingQueueDeadLock : public QMainWindow
{
    Q_OBJECT

public:
    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主界面关闭,而后台进程未释放的原因有很多。这里只是展示了其中一个原因:后台进程中有线程未及时释放导致的,也为遇到同样问题的你提供一个思路。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: qt::blockingqueuedconnectionQt中的一种连接类型,它表示在发送信号时,接收者会在发送者的线程中执行,但是发送者会等待接收者的槽函数执行完毕后才会继续执行。这种连接类型可以保证信号和槽函数的同步执行,但是也可能会导致死锁等问题,需要谨慎使用。 ### 回答2: Qt 中的 `Qt::BlockingQueuedConnection` 是一种信号槽连接类型,可以用于在不同线程之间同步地传递数据。常见的 Qt 信号槽连接方式是 `Qt::AutoConnection`,当信号发送者和接收者位于同一线程时,会使用直接连接(`Qt::DirectConnection`),否则使用队列连接(`Qt::QueueConnection`)。而 `Qt::BlockingQueuedConnection` 则是从队列连接衍生出来的,它在执行信号槽时会阻塞发送者线程,直到接收者线程响应完成。 使用 `Qt::BlockingQueuedConnection` 可以在不开启新线程的情况下,将某些数据的处理和计算任务交给其他线程完成,等待结果返回后继续执行。这种方式常用于需要线程等待子线程处理结果的情况,例如在 UI 线程中调用耗时操作(如网络请求或文件读写),因为 UI 线程不宜阻塞太长时间,否则会造成卡顿,使用 `Qt::BlockingQueuedConnection` 则可以避免这种问题。 在使用时,需要注意以下几个方面: 1. `Qt::BlockingQueuedConnection` 只能用于跨线程之间的信号槽连接; 2. 信号槽函数定义需要与连接方式匹配,否则会编译错误; 3. 在使用阻塞队列连接时,应避免在接收者线程中再次发出信号,否则会导致死锁; 4. 阻塞队列连接是同步的方式,可以造成性能瓶颈,应慎重使用。 综上所述,`Qt::BlockingQueuedConnection` 是一种非常实用的连接方式,能够在不增加线程的情况下实现线程间的同步传递数据,但需要注意合理使用,以避免出现死锁和性能问题。 ### 回答3: Qt的信号和槽机制是Qt中最重要的特性之一,它允许类之间的交互变得非常容易和直观。Qt::BlockingQueuedConnectionQt的一个连接类型,它可以控制信号和槽函数的调用顺序和线程安全性,从而使得线程间通信更加可靠、安全而且简单。 当使用Qt::BlockingQueuedConnection连接信号和槽函数时,如果槽函数和信号函数在同一个线程中,那么它们就会像普通的连接一样被调用,不会有任何影响。但是当信号和槽函数在不同的线程中时,Qt::BlockingQueuedConnection会将槽函数的调用放入到信号所属线程的事件队列中,直到槽函数被执行完成之后,信号函数才会继续执行下去。这样做的好处是,它可以确保槽函数在执行时不会被其他线程所干扰,同时也解决了线程之间的同步问题。 需要注意的是,Qt::BlockingQueuedConnection只能用于非GUI线程,如果在线程中使用会导致GUI死锁。另外,使用这种连接类型会造成一定的延迟和额外的开销,因为每个信号都要阻塞到槽函数执行完毕才能继续执行下去,这可能会影响程序的性能。 在实际应用中,我们可以根据具体的需求来选择不同的连接类型。如果需要处理特别复杂或者耗时的计算,那么我们可以使用Qt::BlockingQueuedConnection来保证线程安全性;如果不需要考虑线程安全性,那么我们可以使用Qt::DirectConnection或者Qt::AutoConnection来进行连接。总体来说,Qt::BlockingQueuedConnection为我们提供了一种更加安全、可靠的线程间通信方式,使得这一过程变得更加简单和可控。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值