先总结一下前面几章:
- 生存线程就是QObject的event及其衍生方法的执行线程。
- 事件循环内部为一个死循环,它会托管当前线程并将当前线程中的事件分发给相应的对象,且对象的事件执行函数(event)所在的执行线程为当前线程。
- 当开启一个新事件循环时,会阻塞当前流程,类似于
while(true)
的效果。但与之不同的是while(true)
在没有退出循环前当前线程会一直阻塞,什么都得不到执行,但事件循环仅仅只是阻塞当前流程,如果当前线程还有其他事件,这些事件仍然能够得到执行。 - 同一个线程中同一时间只会有一个事件循环生效,当一个事件循环生效,并在事件执行函数(event)中再次开启一个新的事件循环时会阻塞老事件循环,新的事件循环会代替老事件循环分发剩余事件。
线程同步
多线程是编程中永远绕不开的事,那么线程同步同样也是编程中永远绕不开的问题。需要线程同步的情况有很多种,有如下一种情况:
- 存在一个业务处理线程,专门处理长耗时任务。
- 主线程有两个长耗时任务,且这两个任务有先后顺序。
那么最常规的编程方案可以是: 业务线程中存在一个任务队列,run函数中不断加锁、从任务队列提取任务执行,并不断循环。外部调用某个函数添加任务,该函数加锁、向任务队列追加并退出函数。
其实锁和队列QT已经自带了,我们完全可以只使用QT提供的接口就能完成这个功能。
- 创建一个继承至QObject的类ObjectTest,并重写QObject的虚函数customEvent,这个函数会处理QEvent::Type的值大于等于QEvent::User的事件
// .h
#pragma once
#include <QObject>
class ObjectTest : public QObject
{
Q_OBJECT
public:
explicit ObjectTest(QObject *parent = nullptr);
void customEvent(QEvent *event) override;
};
//.cpp
#include "ObjectTest.h"
#include <QDebug>
#include <QEvent>
#include <QThread>
ObjectTest::ObjectTest(QObject *parent) : QObject(parent)
{
}
void ObjectTest::customEvent(QEvent *event)
{
qDebug() << event->type() << QThread::currentThread();
}
- 构造一个对象和一个新的线程,并使用moveToThread将对象的生存线程移动至新的线程中,并向对象中抛事件
ObjectTest o; // 构造一个对象o
QThread t; // 构造一个新的线程
t.start(); // 启动该线程
o.moveToThread(&t); // 将对象o的生存线程从主线程移动至新的线程中
qDebug() << "main thread:" << QThread::currentThread();
qApp->postEvent(&o, new QEvent(static_cast<QEvent::Type>(QEvent::User))); // 向对象o中抛一个事件
qApp->postEvent(&o, new QEvent(static_cast<QEvent::Type>(QEvent::User + 1))); // 向对象o中再抛一个新的事件
运行结果
main thread: QThread(0x1f9463ae1e0)
QEvent::User QThread(0x3c039ff6b8)
QEvent::Type(1001) QThread(0x3c039ff6b8)
根据以上结果可以看到,事件的运行线程和主线程不同,达到了多线程的效果,抛出的两个事件完全按照抛出时的循序先后执行。
多线程就不多说了,就是本文章第一句话。先后循序能得以保证是因为postEvent所抛出的事件会追加到事件队列中,而队列是一种先进先出的数据结构。这里没有体现线程安全,因为这里的两个事件都是在主线程中抛出的,按照我的习惯直接看postEvent的源码去证明其线程安全性(QT助手也有明确说明: Note: This function is thread-safe.
),毕竟要做测试的话要花很大的功夫。
postEvent的源码压缩一下并去掉注释如下:
void QCoreApplication::postEvent(QObject *receiver, QEvent *event, int priority)
{
Q_TRACE_SCOPE(QCoreApplication_postEvent, receiver, event, event->type());
if (receiver == nullptr) {
qWarning("QCoreApplication::postEvent: Unexpected null receiver");
delete event;
return;
}
auto locker = QCoreApplicationPrivate::lockThreadPostEventList(receiver);
if (!locker.threadData) {
delete event;
return;
}
QThreadData *data = locker.threadData;
if (receiver->d_func()->postedEvents
&& self && self->compressEvent(event, receiver, &data->postEventList)) {
Q_TRACE(QCoreApplication_postEvent_event_compressed, receiver, event);
return;
}
if (event->type() == QEvent::DeferredDelete)
receiver->d_ptr->deleteLaterCalled = true;
if (event->type() == QEvent::DeferredDelete && data == QThreadData::current()) {
int loopLevel = data->loopLevel;
int scopeLevel = data->scopeLevel;
if (scopeLevel == 0 && loopLevel != 0)
scopeLevel = 1;
static_cast<QDeferredDeleteEvent *>(event)->level = loopLevel + scopeLevel;
}
QScopedPointer<QEvent> eventDeleter(event);
Q_TRACE(QCoreApplication_postEvent_event_posted, receiver, event, event->type());
data->postEventList.addEvent(QPostEvent(receiver, event, priority));
eventDeleter.take();
event->posted = true;
++receiver->d_func()->postedEvents;
data->canWait = false;
locker.unlock();
QAbstractEventDispatcher* dispatcher = data->eventDispatcher.loadAcquire();
if (dispatcher)
dispatcher->wakeUp();
}
加锁和解锁的两行代码为auto locker = QCoreApplicationPrivate::lockThreadPostEventList(receiver);
和locker.unlock();
看到它加个锁都要单独写个函数出来我也就放心了,总比自己调QMutex::lock函数好,你可以信任QT。
上面的方案可以让某一个线程安全的按照循序去执行任务,但是外面没法同步等到任务结束。要么通过信号槽、要么使用回调函数异步的去通知外面的线程。其实有一种办法可以实现同步效果的,并且也是QT提供的接口。使用QMetaObject::invokeMethod
函数,并将第三个参数置为Qt::BlockingQueuedConnection
即可,但是这种办法是依赖了元对象的,并且还有一些注意事项,感兴趣可以查一查,这里不多说,后面有机会说到元对象的时候再介绍这个函数吧。
事件循环应该还有其他用途,短时间内记不太清,后面想到再慢慢补吧。