前言:
之前写过有关事件循环和条件变量的博客:
Qt使用事件循环,信号,stop变量,sleep阻塞,QWaitCondition+QMutex条件变量,退出子线程工作_大橘的博客-CSDN博客_qt stop函数
Qt事件循环(QCoreApplication::processEvents,exec)的应用_大橘的博客-CSDN博客_qcoreapplication::processevents()
主要用于线程间通信,实现子线程控制。
偶然看到一位朋友的博客:
14.QueuedConnection和BlockingQueuedConnection连接方式源码分析_Master Cui的博客-CSDN博客_blockingqueuedconnection
他很了不起,写了很多有深度的文章。从这篇博客中,原本很常用的信号槽机制,我突然想研究一下阻塞队列连接方式的效果。亦即:Qt::BlockingQueuedConnection,connect函数的最后一个参数。
信号槽连接方式:
qt信号槽的连接方式一共就这几种:
Qt::AutoConnection
Qt::DirectConnection
Qt::QueuedConnection
Qt::BlockingQueuedConnection
Qt::UniqueConnection
qt手册以及网络上的介绍太多太详细了。其实很简单,通常我不用这个参数,直接就是auto模式,qt会自动布置。
信号与槽在同一个线程时,默认是direct方式,也就是顺序执行的,发送信号以后立刻执行槽函数,然后执行发信号后面的语句。发信号就好像调用函数一样,执行完函数再执行后面的代码。
信号与槽不再同一个线程时,默认是队列模式,执行顺序受os调度影响,异步执行。发送信号以后不会立刻执行另一个线程的槽函数,而是把发送信号后面的语句一口气执行完,再执行另一个线程的槽。这里涉及事件循环的概念,不再赘述。
同样是信号与槽不再一个线程,还可以采用阻塞队列模式,就是同时具备上两种方式的特点。发送完信号,直接执行槽,然后执行发信号后面的代码。发信号这个线程会阻塞一下,等待槽执行完毕。
最后一种见名知意,不允许多次连接,如果不选这种模式,连接一次,就会触发一次槽。
原本上面这些连接方式不需要特别深入,知道即可。但是我对阻塞队列模式感兴趣,因为它特别像条件变量的效果,QWaitCondition。所以,先看一种场景,事件循环嵌套。
事件循环嵌套:
先看一段简单的代码:
#include "obj.h"
#include <QThread>
#include <QCoreApplication>
#include <QDebug>
Obj::Obj()
{
}
void Obj::f_Start()
{
m_bStop = false;
m_iLoopLevel++;
while (true)
{
QCoreApplication::processEvents();//事件循环
if (m_bStop)//退出判断
{
break;
}
QThread::msleep(200);
}
qDebug() << "Sub thread: event loop exists level: " << m_iLoopLevel;
m_iLoopLevel--;
if (m_iLoopLevel == 0)
{
f_Caller_Wakeup();
qDebug() << "Sub thread: end.";
}
}
void Obj::onStart()//这是一个槽函数,如果多次被触发,会让上面while循环中的eventloop嵌套
{
f_Start();
}
void Obj::onStop()//用于接受停止命令,从而退出while循环,结束子线程
{
qDebug() << "Sub thread: stopping...";
m_bStop = true;
}
同时我做了一个窗体,用于人为控制子线程。
点一次start就触发一层事件循环,点击stop会终止子线程所有循环。
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "obj.h"
#include <QThread>
#include <QDebug>
#include <QTextCodec>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
m_obj = new Obj;
connect(this, SIGNAL(sigStart()), m_obj, SLOT(onStart()));
connect(this, SIGNAL(sigStop()), m_obj, SLOT(onStop()));
m_obj->setParent(nullptr);
QThread *thd = new QThread;
thd->start();
m_obj->moveToThread(thd);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::on_btnStart_clicked()
{
emit sigStart();
}
void MainWindow::on_btnStop_clicked()
{
qDebug() << "Main thread send signal";
emit sigStop();
qDebug() << "Main thread end.";
}
期望场景:
现在开始玩上面的代码,先说理想情况,我希望多次点击start产生循环嵌套以后,点击stop时,子线程逐级退出循环,而且主线程能正确获得已经结束的时机,从而再执行清理代码的时候不会造成野循环。
按照上面代码输出debug文本,我希望是这个顺序:
Main thread send signal.//主线程发信号
Sub thread: stopping...//子线程开始自我终结
Sub thread: event loop exists level:*//可能会有多级
Sub thread: end.//子线程结束
Main thread end.//主线程结束
上面只是理想,如果就按照上面的代码执行,效果不会实现,槽的执行方式默认时队列方式,它会先把主线程执行完,再执行子线程,这样在主线程写清理代码的时机就很重要,如果操作不当,会造成子线程野循环,报错。
Main thread send signal.//主线程发信号
Main thread end.//主线程结束
Sub thread: stopping...//子线程开始终结
Sub thread: event loop exists level: 3
Sub thread: event loop exists level: 2
Sub thread: event loop exists level: 1
Sub thread: end.//子线程结束
就如上面这样,显然不是想要的。如果一定要这样用,在子线程结束时还要给主线程发信号来触发清理操作。
使用阻塞连接方式:
按照阻塞连接的方式改一下主线程代码:
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "obj.h"
#include <QThread>
#include <QDebug>
#include <QTextCodec>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
m_obj = new Obj;
connect(this, SIGNAL(sigStart()), m_obj, SLOT(onStart()));
//使用阻塞连接方式,就改这一个地方,只是加了一个参数
connect(this, SIGNAL(sigStop()), m_obj, SLOT(onStop()), Qt::BlockingQueuedConnection);
m_obj->setParent(nullptr);
QThread *thd = new QThread;
thd->start();
m_obj->moveToThread(thd);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::on_btnStart_clicked()
{
emit sigStart();
}
void MainWindow::on_btnStop_clicked()
{
qDebug() << "Main thread send signal.";
emit sigStop();
qDebug() << "Main thread end.";
}
只在connect的时候加了一个参数而已,再看效果。
Main thread send signal.//主线程发信号
Sub thread: stopping...//子线程执行槽,执行完槽回到eventloop
Main thread end.//主线程继续结束
Sub thread: event loop exists level: 3//子线程退出逐级循环
Sub thread: event loop exists level: 2
Sub thread: event loop exists level: 1
Sub thread: end.//子线程结束
看效果有点那个意思,但是子线程一旦执行完槽函数,它就回到自己最近的一层事件循环,这就算处理完消息队列了,所以接着会执行主线程的代码。因此还无法实现最初的想法。所以就有了使用条件变量的方法。
使用条件变量:
子线程对象定义,加入条件变量和响应的锁:
#ifndef OBJ_H
#define OBJ_H
#include <QObject>
#include <QWaitCondition>
#include <QMutex>
class Obj : public QObject
{
Q_OBJECT
public:
Obj();
QWaitCondition m_condition;//条件变量
QMutex m_mutex;//互斥锁
void f_Caller_Wait();//用于主线程阻塞
void f_Caller_Wakeup();//用于主线程唤醒
private slots:
void onStart();
void onStop();
private:
bool m_bStop = true;
int m_iLoopLevel = 0;
void f_Start();
};
#endif // OBJ_H
子线程实现:
#include "obj.h"
#include <QThread>
#include <QCoreApplication>
#include <QDebug>
Obj::Obj()
{
}
void Obj::f_Start()
{
m_bStop = false;
m_iLoopLevel++;
while (true)
{
QCoreApplication::processEvents();
if (m_bStop)
{
break;
}
QThread::msleep(200);
}
qDebug() << "Sub thread: event loop exists level: " << m_iLoopLevel;
m_iLoopLevel--;
if (m_iLoopLevel == 0)
{
f_Caller_Wakeup();//退出最后一层循环后,唤醒主线程
qDebug() << "Sub thread: end.";
}
}
void Obj::onStart()
{
f_Start();
}
void Obj::onStop()
{
qDebug() << "Sub thread: stopping...";
m_bStop = true;
}
void Obj::f_Caller_Wait()
{
qDebug() << "Main thread wait...";
m_mutex.lock();
m_condition.wait(&m_mutex);
m_mutex.unlock();
}
void Obj::f_Caller_Wakeup()
{
qDebug() << "Main thread continue.";
m_mutex.lock();
m_condition.wakeAll();
m_mutex.unlock();
}
主线程实现:
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "obj.h"
#include <QThread>
#include <QDebug>
#include <QTextCodec>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
m_obj = new Obj;
connect(this, SIGNAL(sigStart()), m_obj, SLOT(onStart()));
connect(this, SIGNAL(sigStop()), m_obj, SLOT(onStop()));//使用默认连接方式
m_obj->setParent(nullptr);
QThread *thd = new QThread;
thd->start();
m_obj->moveToThread(thd);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::on_btnStart_clicked()
{
emit sigStart();
}
void MainWindow::on_btnStop_clicked()
{
qDebug() << "Main thread send signal.";
emit sigStop();
m_obj->f_Caller_Wait();//主线程等待
qDebug() << "Main thread end.";
}
执行效果如下:
Main thread send signal.//主线程发信号
Main thread wait...//主线程等待
Sub thread: stopping...//子线程开始终结
Sub thread: event loop exists level: 6
Sub thread: event loop exists level: 5
Sub thread: event loop exists level: 4
Sub thread: event loop exists level: 3
Sub thread: event loop exists level: 2
Sub thread: event loop exists level: 1
Main thread continue.//唤醒主线程
Sub thread: end.//子线程终结
Main thread end.//主线程终结
这就实现最初的想法了。
意义:
之所以要这样做,我是希望主线程能够在终结子线程操作的时候能够保持时序,终结子线程行为之后,可以马上执行清理操作,而不会报错。
如果不这样,发完信号就清理,子线程还没来得及停止所有操作,就已经被释放内存,内存倒是没泄露,可是正在执行的循环没有跳出,就是野循环,早晚会耗尽资源卡死,甚至直接报错。
当然也可以主线程发完信号之后不要马上清理,子线程完成后发信号通知主线程已经结束,再清理。下面试试这种方式。
子线程回复方式:
子线程加一个sigStopped信号,发给主线程。主要看一下实现代码。
子线程实现:
#include "obj.h"
#include <QThread>
#include <QCoreApplication>
#include <QDebug>
Obj::Obj()
{
}
void Obj::f_Start()
{
m_bStop = false;
m_iLoopLevel++;
while (true)
{
QCoreApplication::processEvents();
if (m_bStop)
{
break;
}
QThread::msleep(200);
}
qDebug() << "Sub thread: event loop exists level: " << m_iLoopLevel;
m_iLoopLevel--;
if (m_iLoopLevel == 0)
{
qDebug() << "Sub thread: end.";
emit sigStopped();//退出最后一层循环后,通知主线程
}
}
void Obj::onStart()
{
f_Start();
}
void Obj::onStop()
{
qDebug() << "Sub thread: stopping...";
m_bStop = true;
}
主线程实现:
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "obj.h"
#include <QThread>
#include <QDebug>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
m_obj = new Obj;
connect(this, SIGNAL(sigStart()), m_obj, SLOT(onStart()));
connect(this, SIGNAL(sigStop()), m_obj, SLOT(onStop()));
connect(m_obj, SIGNAL(sigStopped()), this, SLOT(onSubStopped()));//增加一个回复连接
m_obj->setParent(nullptr);
QThread *thd = new QThread;
thd->start();
m_obj->moveToThread(thd);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::on_btnStart_clicked()
{
emit sigStart();
}
void MainWindow::on_btnStop_clicked()
{
qDebug() << "Main thread send signal.";
emit sigStop();//发送完信号什么也不做,等待子线程回复
}
void MainWindow::onSubStopped()//子线程发回已终止信号之后被触发
{
qDebug() << "Main thread end.";
}
执行效果:
Main thread send signal.//主线程发信号
Sub thread: stopping...//子线程开始自我终结
Sub thread: event loop exists level: 3
Sub thread: event loop exists level: 2
Sub thread: event loop exists level: 1
Sub thread: end.//子线程终止
Main thread end.//主线程终止
看效果还是不错的。
按说最后一种异步方式更灵活,但是看情况,太多的异步交互不太容易阅读代码。