Qt源码阅读——事件循环

文章目录

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

源码版本:Qt 6.5.0

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

cpp 复制代码
QApplicaton app(argc, argv);
...
app.exec();

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

一、 QCoreApplication的exec()实现

cpp 复制代码
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()实现

cpp 复制代码
int QEventLoop::exec(ProcessEventsFlags flags)
{
    Q_D(QEventLoop);
    auto threadData = d->threadData.loadRelaxed();

    //we need to protect from race condition with QThread::exit
    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;
    }

    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 warning
            d->inExec = false;
            --threadData->loopLevel;
        }
    };
    LoopReference ref(d, locker);

    // remove posted quit events when entering a new event loop
    QCoreApplication *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指针用法

cpp 复制代码
Q_D(QEventLoop)

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

宏展开就是:

cpp 复制代码
QEventLoopPrivate* const d = d_func();

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

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

2. 获取线程数据

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

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

cpp 复制代码
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

翻译一下就是:

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

QObjectPrivate继承自QObjectData,如下:

cpp 复制代码
class Q_CORE_EXPORT QObjectPrivate : public QObjectData
{
public:
    Q_DECLARE_PUBLIC(QObject)
...
};

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

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

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

cpp 复制代码
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. 加锁和判断

cpp 复制代码
   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

cpp 复制代码
 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 warning
         d->inExec = false;
         --threadData->loopLevel;
     }
 };
 LoopReference ref(d, locker);

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

4.1 LoopReference的构造函数:

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

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

cpp 复制代码
auto threadData = d->threadData.loadRelaxed();
++threadData->loopLevel;

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

4.2 QThreadData的成员变量

cpp 复制代码
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的析构函数

cpp 复制代码
 ~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 warning
            d->inExec = false;
            --threadData->loopLevel;
        }
     Q_ASSERT_X(eventLoop == d->q_func(), "QEventLoop::exec()", "internal error");
     Q_UNUSED(eventLoop); // --release warning
     d->inExec = false;
     --threadData->loopLevel;
 }

析构时将执行了逆操作:

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

4.4 小结

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

cpp 复制代码
LoopReference ref(d, locker);

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

在构造时:

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

在析构时执行逆操作:

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

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

5. 事件循环

cpp 复制代码
    // remove posted quit events when entering a new event loop
    QCoreApplication *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循环:

cpp 复制代码
while (!d->exit.loadAcquire())
    processEvents(flags | WaitForMoreEvents | EventLoopExec);

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

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

然后返回计数码d->returnCode

5.1 processEvents()

processEvents()的实现如下:

cpp 复制代码
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中存有以下重要变量:

    cpp 复制代码
    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
相关推荐
云和数据.ChenGuang21 分钟前
Django 应用安装脚本 – 如何将应用添加到 INSTALLED_APPS 设置中 原创
数据库·django·sqlite
woshilys1 小时前
sql server 查询对象的修改时间
运维·数据库·sqlserver
Hacker_LaoYi1 小时前
SQL注入的那些面试题总结
数据库·sql
2401_857439692 小时前
SSM 架构下 Vue 电脑测评系统:为电脑性能评估赋能
开发语言·php
建投数据2 小时前
建投数据与腾讯云数据库TDSQL完成产品兼容性互认证
数据库·腾讯云
SoraLuna2 小时前
「Mac畅玩鸿蒙与硬件47」UI互动应用篇24 - 虚拟音乐控制台
开发语言·macos·ui·华为·harmonyos
xlsw_2 小时前
java全栈day20--Web后端实战(Mybatis基础2)
java·开发语言·mybatis
Hacker_LaoYi3 小时前
【渗透技术总结】SQL手工注入总结
数据库·sql
岁月变迁呀3 小时前
Redis梳理
数据库·redis·缓存
独行soc3 小时前
#渗透测试#漏洞挖掘#红蓝攻防#护网#sql注入介绍06-基于子查询的SQL注入(Subquery-Based SQL Injection)
数据库·sql·安全·web安全·漏洞挖掘·hw