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


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

    1、错误代码示例

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

    /** BlockingQueueDeadLock.h **/
    #pragma once
    
    #include 
    #include "ui_BlockingQueueDeadLock.h"
    #include 
    
    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 
    
    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();
    	}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79

    上面短短几十行代码竟会导致当我关闭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();
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    线程的主函数就是一个while循环,在我们BlockingQueueDeadLock析构的时候会将标识符m_StopFlag置为true;按道理讲该while循环应该很快的结束并返回。

    但是!但是!但是!我们是否忽略了emit这个信号发射的是如何connect的呢?

    connect(this, &BlockingQueueDeadLock::SendBlockingQueueSignal,
    		this, &BlockingQueueDeadLock::RecvBlockingQueueSignal, Qt::BlockingQueuedConnection);
    
    • 1
    • 2

    非常非常奇怪的是为什么connect最后一个参数是Qt::BlockingQueuedConnection呢?我看到这个代码也会很奇怪,可能之前的开发者认为发送信号和接受信号的slot不在同一个线程吧!!

    3、解决方案

    奇怪的地方必有妖,没错就是Qt::BlockingQueuedConnection这里有问题。

    先看Qt官方文档的解释:
    在这里插入图片描述

    非常明确的指出了发射的信号与接收的槽不能在同一个线程里,否者会导致应用死锁。

    想要了解Qt BlockingQueuedConnection源码的同学可以看下面的文章:

    14.QueuedConnection和BlockingQueuedConnection连接方式源码分析_Master Cui的博客-CSDN博客
    在这里插入图片描述

    明确的指出了使用BlockingQueuedConnection同一个线程时死锁的原因。

    因此,我们只需要将Qt::BlockingQueuedConnection最后的参数去除,使用默认参数即可。

    这就是我发现的Qt主界面关闭,而后台进程未释放的问题;还是由于后台进程中的某个线程没有释放,而该线程没有结束又是由于Qt::BlockingQueuedConnection死锁导致的。

    4、总结

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

  • 相关阅读:
    SQL Server 数据库变成单个用户怎么办
    竞赛 基于大数据的时间序列股价预测分析与可视化 - lstm
    开源操作系统大全
    【linux命令讲解大全】113.网络接口和系统设备监测工具ifstat和iostat的使用
    Ribbon负载均衡
    pytho-numpy-数组切片存取元素
    CLIP(Contrastive Language-Image Pretraining)
    JVM垃圾回收器
    《代码大全2》第8章 防御式编程
    【PCA降维】在人脸识别中的应用
  • 原文地址:https://blog.csdn.net/youzai2017/article/details/132746319