当前位置: 首页 > article >正文

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.kler.cn/news/334974.html

相关文章:

  • MQTTnet.Extensions.ManagedClient客户端连接
  • 【数据结构】双向链表(Doubly Linked List)
  • Spring Boot ⽇志
  • Stable Diffusion 常用大模型及其特点
  • 数据结构之树(3)
  • 简单认识 redis -3 -其他命令
  • HDU Sit sit sit (区间DP+组合数)
  • 打造自己的RAG解析大模型:Windows部署OCR服务(可商业应用)
  • springboot网上商城源码分享
  • Chromium 中JavaScript Console API接口c++代码实现
  • Goland 设置GOROOT报错 The selected directory is not a valid home for Go SDK
  • C语言:预编译过程的剖析
  • npm、yarn、pnpm之间的区别
  • 构建数字化生态平台,开启企业新未来
  • 冯诺依曼体系|操作系统
  • 服务器租用与托管注意事项有哪些?
  • [Day 84] 區塊鏈與人工智能的聯動應用:理論、技術與實踐
  • 【包教包会】2D图片实现3D透视效果(支持3.x、支持原生、可合批)
  • ElasticSearch备考 -- Multi match
  • 代码随想录算法训练营第35天|1049.最后一块石头的重量II、494.目标和、474.一和零