Qt源码阅读——事件循环

news/2024/10/7 13:11:55 标签: qt, 数据库, 开发语言

文章目录

  • 一、 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::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指针用法

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 warning
         d->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 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的作用就搞清楚了,

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 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循环:

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.niftyadmin.cn/n/5692892.html

相关文章

数据分析之Spark框架介绍

文章目录 概述一、发展历程与背景二、核心特点三、生态系统与组件四、应用场景五、与其他大数据技术的比较 核心概念1. 弹性分布式数据集&#xff08;RDD, Resilient Distributed Dataset&#xff09;2. 转换&#xff08;Transformations&#xff09;和动作&#xff08;Actions…

【RockyLinux 9.4】CentOS也可以用。安装教程(使用U盘,避免踩坑简略版本)

一、制作一个镜像安装盘 1.下载镜像&#xff08;本教程使用9.4版本&#xff09; 官网&#xff1a; https://rockylinux.org/zh-CN 2.使用 UltraISO&#xff0c;制作写入硬盘镜像 二、调整相关参数&#xff0c;准备进入安装流程 1.关闭 Secure Boot&#xff08;BIOS 里面关…

尚硅谷 rabbitmq 2024 第34-37 延时队列 答疑

rabbitmq可以建立延时队列&#xff0c;redis也可以&#xff0c;就像一个下订单一天后字符超时一样&#xff0c;那这个为什么不用java的sleep或者定时器呢&#xff0c;非要搞个中间件里面去搞&#xff1f; 使用中间件&#xff08;如RabbitMQ或Redis&#xff09;来实现延时队列而…

vite学习教程02、vite+vue2配置环境变量

文章目录 前言1、安装依赖2、配置环境变量3、应用环境变量4、运行和构建项目资料获取 前言 博主介绍&#xff1a;✌目前全网粉丝3W&#xff0c;csdn博客专家、Java领域优质创作者&#xff0c;博客之星、阿里云平台优质作者、专注于Java后端技术领域。 涵盖技术内容&#xff1…

安装最新 MySQL 8.0 数据库(教学用)

安装 MySQL 8.0 数据库&#xff08;教学用&#xff09; 文章目录 安装 MySQL 8.0 数据库&#xff08;教学用&#xff09;前言MySQL历史一、第一步二、下载三、安装四、使用五、语法总结 前言 根据 DB-Engines 网站的数据库流行度排名&#xff08;2024年&#xff09;&#xff0…

Spring Boot医院管理系统:数据驱动的医疗

3系统分析 3.1可行性分析 通过对本医院管理系统实行的目的初步调查和分析&#xff0c;提出可行性方案并对其一一进行论证。我们在这里主要从技术可行性、经济可行性、操作可行性等方面进行分析。 3.1.1技术可行性 本医院管理系统采用JAVA作为开发语言&#xff0c;Spring Boot框…

Cocos_鼠标滚轮放缩地图

文章目录 前言一、环境二、版本一_code2.分析类属性方法详细分析详细分析onLoad()onMouseWheel(event)详细分析 总结 前言 学习笔记&#xff0c;请多多斧正。 一、环境 通过精灵rect放置脚本实现鼠标滚轮放缩地图。 二、版本一_code import { _decorator, Component, Node }…

旅游心动盲盒:开启个性化旅行新体验

嘿&#xff0c;宝子们&#xff01;在如今这个数字化时代呀&#xff0c;文心智能体可是给咱们的生活带来了超多便利和创新呢。今天呀&#xff0c;我来给大家介绍一款超棒的智能体——旅游心动盲盒&#xff0c;它肯定能给你的旅行带来全新的惊喜和超个性化的体验哟。 一、项目背…