Qt源码阅读——事件循环

server/2024/10/21 10:04:42/

文章目录

  • 一、 QCoreApplication的exec()实现
  • 二、 QEventLoop的exec()实现
    • 1. D指针用法
    • 2. 获取线程数据
    • 3. 加锁和判断
    • 4. 局部类`LoopReference`
      • 4.1 `LoopReference`的构造函数:
      • 4.2 QThreadData的成员变量
      • 4.3 `LoopReference`的析构函数
      • 4.4 小结
    • 5. 事件循环
      • 5.1 processEvents()
  • 三、小结

源码版本:Qt 6.5.0

主程序中一般都少不了这两行代码:

QApplicaton app(argc, argv);
...
app.exec();

下面来跟踪一下它的实现。

一、 QCoreApplication的exec()实现

int QCoreApplication::exec()
{if (!QCoreApplicationPrivate::checkInstance("exec"))return -1;QThreadData *threadData = self->d_func()->threadData.loadAcquire();if (threadData != QThreadData::current()) {qWarning("%s::exec: Must be called from the main thread", self->metaObject()->className());return -1;}if (!threadData->eventLoops.isEmpty()) {qWarning("QCoreApplication::exec: The event loop is already running");return -1;}threadData->quitNow = false;QEventLoop eventLoop;self->d_func()->in_exec = true;self->d_func()->aboutToQuitEmitted = false;int returnCode = eventLoop.exec(QEventLoop::ApplicationExec);threadData->quitNow = false;if (self)self->d_func()->execCleanup();return returnCode;
}

可以看到application的exec也是调用了QEventLoop的exec。

这里不太重要,那么就继续看QEventLoop的实现。

二、 QEventLoop的exec()实现

int QEventLoop::exec(ProcessEventsFlags flags)
{Q_D(QEventLoop);auto threadData = d->threadData.loadRelaxed();//we need to protect from race condition with QThread::exitQMutexLocker locker(&static_cast<QThreadPrivate *>(QObjectPrivate::get(threadData->thread.loadAcquire()))->mutex);if (threadData->quitNow)return -1;if (d->inExec) {qWarning("QEventLoop::exec: instance %p has already called exec()", this);return -1;}struct LoopReference {QEventLoopPrivate *d;QMutexLocker<QMutex> &locker;bool exceptionCaught;LoopReference(QEventLoopPrivate *d, QMutexLocker<QMutex> &locker) : d(d), locker(locker), exceptionCaught(true){d->inExec = true;d->exit.storeRelease(false);auto threadData = d->threadData.loadRelaxed();++threadData->loopLevel;threadData->eventLoops.push(d->q_func());locker.unlock();}~LoopReference(){if (exceptionCaught) {qWarning("Qt has caught an exception thrown from an event handler. Throwing\n""exceptions from an event handler is not supported in Qt.\n""You must not let any exception whatsoever propagate through Qt code.");}locker.relock();auto threadData = d->threadData.loadRelaxed();QEventLoop *eventLoop = threadData->eventLoops.pop();Q_ASSERT_X(eventLoop == d->q_func(), "QEventLoop::exec()", "internal error");Q_UNUSED(eventLoop); // --release warningd->inExec = false;--threadData->loopLevel;}};LoopReference ref(d, locker);// remove posted quit events when entering a new event loopQCoreApplication *app = QCoreApplication::instance();if (app && app->thread() == thread())QCoreApplication::removePostedEvents(app, QEvent::Quit);while (!d->exit.loadAcquire())processEvents(flags | WaitForMoreEvents | EventLoopExec);ref.exceptionCaught = false;return d->returnCode.loadRelaxed();
}

后文将对QEventLoop::exec()实现的每一行按顺序进行解读。

1. D指针用法

Q_D(QEventLoop)

Qt中非常常见的D指针用法(pimpl惯用法),用于在类Xxx中访问隐藏部分类XxxPrivate的实现。

宏展开就是:

QEventLoopPrivate* const d = d_func();

后续可以用变量d来访问QEvnetLoop的隐藏实现部分,也即在类QEvnetLoopPrivate的部分。

相对应的还有Q指针用法Q_Q,用于在XxxPrivate中访问Xxx的实现。

2. 获取线程数据

auto threadData = d->threadData.loadRelaxed();

这里的d->threadData定义在类QObjectPrivate中,如下:

public:mutable ExtraData *extraData; // extra data set by the user// This atomic requires acquire/release semantics in a few places,// e.g. QObject::moveToThread must synchronize with QCoreApplication::postEvent,// because postEvent is thread-safe.// However, most of the code paths involving QObject are only reentrant and// not thread-safe, so synchronization should not be necessary there.QAtomicPointer<QThreadData> threadData; // id of the thread that owns the object

翻译一下就是:

mutable ExtraData *extraData; // 用户设置的额外数据
// 此原子操作在某些地方需要获取/释放语义,
// 例如,QObject::moveToThread 必须与 QCoreApplication::postEvent 同步,
// 因为 postEvent 是线程安全的。
// 然而,涉及 QObject 的大多数代码路径只是可重入的并且不是线程安全的,
// 所以在这些地方不需要同步。
QAtomicPointer<QThreadData> threadData; // 拥有该对象的线程的 ID

QObjectPrivate继承自QObjectData,如下:

class Q_CORE_EXPORT QObjectPrivate : public QObjectData
{
public:Q_DECLARE_PUBLIC(QObject)
...
};

而在QObject中也有一个指针成员变量存储了QObjectData,如下:

...
protected:QObject(QObjectPrivate &dd, QObject *parent = nullptr);
protected:QScopedPointer<QObjectData> d_ptr;
...

并且在构造时将d_ptr赋值为了QObjectPrivate

QObject::QObject(QObject *parent): QObject(*new QObjectPrivate, parent)
{
}
QObject::QObject(QObjectPrivate &dd, QObject *parent): d_ptr(&dd)
{Q_ASSERT_X(this != parent, Q_FUNC_INFO, "Cannot parent a QObject to itself");Q_D(QObject);d_ptr->q_ptr = this;auto threadData = (parent && !parent->thread()) ? parent->d_func()->threadData.loadRelaxed() : QThreadData::current();threadData->ref();...
}

总之明白一点:每个QObject类都有线程数据,记录了它的线程信息,可以知道这个类属于哪一个线程。

至于其中的类(模板)QAtomicPointer继承自类(模板)QBasicAtomicPointerQBasicAtomicPointer组合了变量QAtomicOpsQAtomicOps相当于std::atomic
本质上是用std::atomic存储了模板类型T的指针,保证访问的原子性。

所以auto threadData = d->threadData.loadRelaxed();这一行代码也仅仅是获取QObjectPrivate中的线程数据,原子性地。

而且QEventLoop也是继承自QObject的,通过这一行代码,至少可以得知Qt中线程和事件循环的关系的一个结论:
每个事件循环都都自己所属的线程,拥有相关的线程数据

在启动事件循环时首先会去获取线程数据。

3. 加锁和判断

   QMutexLocker locker(&static_cast<QThreadPrivate *>(QObjectPrivate::get(threadData->thread.loadAcquire()))->mutex);if (threadData->quitNow)return -1;if (d->inExec) {qWarning("QEventLoop::exec: instance %p has already called exec()", this);return -1;}

接下来,对线程数据进行了加锁。进行简单判断是否要退出、提前结束。

4. 局部类LoopReference

 struct LoopReference {QEventLoopPrivate *d;QMutexLocker<QMutex> &locker;bool exceptionCaught;LoopReference(QEventLoopPrivate *d, QMutexLocker<QMutex> &locker) : d(d), locker(locker), exceptionCaught(true){d->inExec = true;d->exit.storeRelease(false);auto threadData = d->threadData.loadRelaxed();++threadData->loopLevel;threadData->eventLoops.push(d->q_func());locker.unlock();}~LoopReference(){if (exceptionCaught) {qWarning("Qt has caught an exception thrown from an event handler. Throwing\n""exceptions from an event handler is not supported in Qt.\n""You must not let any exception whatsoever propagate through Qt code.");}locker.relock();auto threadData = d->threadData.loadRelaxed();QEventLoop *eventLoop = threadData->eventLoops.pop();Q_ASSERT_X(eventLoop == d->q_func(), "QEventLoop::exec()", "internal error");Q_UNUSED(eventLoop); // --release warningd->inExec = false;--threadData->loopLevel;}};LoopReference ref(d, locker);

接下来定义了一个局部类LoopReference,并创建了一个栈变量ref

4.1 LoopReference的构造函数:

构造函数传入了一个事件循环(的隐藏实现部分,QEventLoopPrivate *),通过d->inExec = true;将事件循环标记为在执行中,然后将d->exit赋值为false

然后将线程数据中的loopLevel加1,大概是递增了循环层深,对应这两个代码:

auto threadData = d->threadData.loadRelaxed();
++threadData->loopLevel;

然后向线程数据threadData中push了一个QEventLoop*,这里我们需要看下QThreadData的实现来了解这行push在干什么:

4.2 QThreadData的成员变量

class QThreadData
{
public:QThreadData(int initialRefCount = 1);...
public:int loopLevel;int scopeLevel;QStack<QEventLoop *> eventLoops;QPostEventList postEventList;QAtomicPointer<QThread> thread;QAtomicPointer<void> threadId;QAtomicPointer<QAbstractEventDispatcher> eventDispatcher;QList<void *> tls;bool quitNow;bool canWait;bool isAdopted;bool requiresCoreApplication;
};

原来QThreadData中用栈存储了一系列的QEventLoop,还有一个QPostEventList posrtEventList;,这个大概是用来存放post的事件的。

事件的发送有sendpost两种机制,send的事件会立刻执行,post的则需要放到队列中,靠事件循环去推动。所以并没有看到sendEventList, 灰常合理。

同时里面还有:

  1. 线程thread
  2. 线程idthreadId
  3. 事件分发器eventDispatcher(底层实现与平台相关)。
  4. tls???大概是用于Thread Local Storaged的。

还有一些变量暂时没用到,简单看下即可,比如requiresCoreApplication大概是用于判断是否需要QCoreApplication

4.3 LoopReference的析构函数

 ~LoopReference(){if (exceptionCaught) {qWarning("Qt has caught an exception thrown from an event handler. Throwing\n""exceptions from an event handler is not supported in Qt.\n""You must not let any exception whatsoever propagate through Qt code.");}locker.relock();auto threadData = d->threadData.loadRelaxed();~LoopReference(){if (exceptionCaught) {qWarning("Qt has caught an exception thrown from an event handler. Throwing\n""exceptions from an event handler is not supported in Qt.\n""You must not let any exception whatsoever propagate through Qt code.");}locker.relock();auto threadData = d->threadData.loadRelaxed();QEventLoop *eventLoop = threadData->eventLoops.pop();Q_ASSERT_X(eventLoop == d->q_func(), "QEventLoop::exec()", "internal error");Q_UNUSED(eventLoop); // --release warningd->inExec = false;--threadData->loopLevel;}Q_ASSERT_X(eventLoop == d->q_func(), "QEventLoop::exec()", "internal error");Q_UNUSED(eventLoop); // --release warningd->inExec = false;--threadData->loopLevel;}

析构时将执行了逆操作:

  1. QEventLoop *eventLoop = threadData->eventLoops.pop();
    • 从线程数据中弹出一个时间循环。
  2. d->inExec = false;
    • 将该事件循环标记为未在执行。
  3. --threadData->loopLevel;
    • 递减事件循环层深。

4.4 小结

至此,局部类LoopReference的作用就搞清楚了,

LoopReference ref(d, locker);

上面这行代码的作用就是按照RAII原则,

在构造时:

  1. 将传入的事件循环标记为在执行中。
  2. 将事件循环压放入线程数据(threadData)的事件循环栈中。
  3. 将线程数据(threadData)中的循环层深loopLevel加1。

在析构时执行逆操作:

  1. 将传入的事件循环标记为未在执行。
  2. 从线程数据(threadData)的事件循环栈中弹出一个事件循环。
  3. 将线程数据(threadData)的循环层深loopLevel减1。

至于这些改动/标记在何处被使用,暂时还不得而知。

5. 事件循环

    // remove posted quit events when entering a new event loopQCoreApplication *app = QCoreApplication::instance();if (app && app->thread() == thread())QCoreApplication::removePostedEvents(app, QEvent::Quit);while (!d->exit.loadAcquire())processEvents(flags | WaitForMoreEvents | EventLoopExec);ref.exceptionCaught = false;return d->returnCode.loadRelaxed();

剩下的代码只有这么多。

其中进行了判断:如果app的当前线程和该事件循环处于同一线程,就移除app中的Quit事件。

QCoreApplication也继承自QObject,其中的QObjectPrivate中的QThreadData部分存有一个QList<QPostEvent> postEvetList

暂时想不通为什么会有、哪里来的Quit事件,不过这两行代码本身较容易理解。

最后来到事件循环的核心部分:一个while循环:

while (!d->exit.loadAcquire())processEvents(flags | WaitForMoreEvents | EventLoopExec);

简单粗暴,直到d->exittrue时才会退出循环。否则就继续调用processEvents,并传入flags。

循环退出后,将ref.exceptionCaught赋为false,没什么大作用,只会让LoopReference析构时不会警告。

然后返回计数码d->returnCode

5.1 processEvents()

processEvents()的实现如下:

bool QEventLoop::processEvents(ProcessEventsFlags flags)
{Q_D(QEventLoop);auto threadData = d->threadData.loadRelaxed();if (!threadData->hasEventDispatcher())return false;return threadData->eventDispatcher.loadRelaxed()->processEvents(flags);
}

可见其中也是调用了事件循环所属线程的eventDispatcherprocessEvents()

eventDispatcher的类型如下:
QAtomicPointer<QAbstractEventDispatcher> eventDispatcher;

事件分发器的实现与平台相关,这一块的实现会再开一篇。

三、小结

  1. Qt一般将类分为公开部分和隐藏部分,例如QWidgetQWidgetPrivate
  2. Qt中宏Q_D用于在公开类访问隐藏实现部分(获取d指针),Q_Q用于在隐藏实现部分访问公开部分(获取q指针)。
  3. 每个QObject背后都存有一个QObjectData指针,在构造时用QObjectData *为其赋值;QObjectPrivate继承自QObejctData,其中存有成员变量QThreadData,记录了线程数据。
  4. 事件循环QEventLoop继承自QObject,所以每个事件循环都有所属线程。(QCoreApplication和其他类同理)
  5. 线程数据QThreadData中存有以下重要变量:
    int loopLevel; // 事件循环层深
    int scopeLevel;QStack<QEventLoop *> eventLoops; //每个线程对应多个事件循环
    QPostEventList postEventList; // 线程的事件队列
    QAtomicPointer<QThread> thread; // 线程
    QAtomicPointer<void> threadId; // 线程ID
    QAtomicPointer<QAbstractEventDispatcher> eventDispatcher; //事件分发器
    QList<void *> tls; //Thread Local Storage
    

http://www.ppmy.cn/server/128272.html

相关文章

【代码随想录Day29】贪心算法Part03

134. 加油站 题目链接/文章讲解&#xff1a;代码随想录 视频讲解&#xff1a;贪心算法&#xff0c;得这么加油才能跑完全程&#xff01;LeetCode &#xff1a;134.加油站_哔哩哔哩_bilibili class Solution {public int canCompleteCircuit(int[] gas, int[] cost) {// 创建一…

深入了解 Ne10:优化 ARM 处理器的数字信号处理库

目录 引言什么是 Ne10&#xff1f;Ne10 的核心功能Ne10 的架构安装与使用 Ne10使用场景结论 引言 在现代计算中&#xff0c;数字信号处理&#xff08;DSP&#xff09;成为许多应用的重要组成部分&#xff0c;尤其是在音频、视频和图像处理领域。对于 ARM 处理器&#xff0c;Ne…

ZLMediaKit编译运行

ZLMediaKit-github官网 快速开始 代码依赖与版权声明 MediaServer支持的HTTP MediaServer支持的HTTP HOOK API cd ZLMediaKit mkdir build cd build cmake … && make -j20 cd ZLMediaKit/release/linux/Debug ./MediaServer //./MediaServer -h 查看 //./MediaSe…

Arthas match Elasticsearch

环境&#xff1a;windows elasticsearch:6.5.4 启动完Elasticsearch后&#xff0c;使用 arthas 遇到报错 java.security.AccessControlException: Access Denied 解决方法&#xff1a; JDK 所在目录下的目录jre/lib/security&#xff0c;修改java.policy文件&#xff0c;尾…

在Docker中运行微服务注册中心Eureka

1、Docker简介&#xff1a; 作为开发者&#xff0c;经常遇到一个头大的问题&#xff1a;“在我机器上能运行”。而将SpringCloud微服务运行在Docker容器中&#xff0c;避免了因环境差异带来的兼容性问题&#xff0c;能够有效的解决此类问题。 通过Docker&#xff0c;开发者可…

安卓13设置删除网络和互联网选项 android13隐藏设置删除网络和互联网选项

总纲 android13 rom 开发总纲说明 文章目录 1.前言2.问题分析3.代码分析4.代码修改4.1修改方法14.2修改方法25.编译6.彩蛋1.前言 有些客户不想让用户修改默认的网络配置,禁止用户进入里面调整网络相关的配置。 2.问题分析 像这个问题,我们有好几种方法去处理,这种需求一般…

Python批量下载PPT模块并实现自动解压

日常工作中&#xff0c;我们总是找不到合适的PPT模板而烦恼。即使有免费的网站可以下载&#xff0c;但是一个一个地去下载&#xff0c;然后再批量解压进行查看也非常的麻烦&#xff0c;有没有更好方法呢&#xff1f; 今天&#xff0c;我们利用Python来爬取一个网站上的PPT&…

TSV(Through Silicon Via)即硅通孔技术和DFT

一种集成电路制造中的先进封装技术。它通过在芯片上穿孔并填充导电材料&#xff08;如铜、钨、多晶硅等&#xff09;&#xff0c;实现芯片内、芯片间以及芯片与封装之间的垂直连接。以下是TSV的主要制造工艺流程&#xff1a; 硅片准备&#xff1a;选择合适的硅片作为开始工艺的…