Qt源码阅读——事件循环

53 篇文章 0 订阅

源码版本: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
    
Qt事件循环是一个非常重要的机制,它负责接收和分发事件,保证 Qt 应用程序的正常运行。下面是简单的 Qt 事件循环源码分析: 1. Qt事件循环是通过 `QCoreApplication::exec()` 方法启动的。该方法首先会创建一个 `QEventLoop` 对象,然后进入一个无限循环。 2. 在事件循环中,`QEventLoop` 对象通过调用 `QCoreApplication::processEvents()` 方法来处理当前队列中的事件。该方法会检查是否有待处理的事件,如果没有,则线程会进入休眠状态,等待新的事件到来。 3. 当一个事件到来时,Qt 会根据事件的类型和目标对象,将事件分发给正确的接收者进行处理。接收者可以是窗口部件、控件、布局等。 4. 对于每个事件,Qt 会调用接收者的对应方法来处理。例如,对于鼠标点击事件,Qt 会调用接收者的 `mousePressEvent()` 方法来处理。 5. 在事件处理过程中,如果需要进行其他操作(如更新界面、执行定时器等),Qt 会将这些操作添加到事件队列中。 6. 当所有待处理的事件都被处理完毕后,Qt 会通过调用 `QCoreApplication::quit()` 方法退出事件循环,程序结束运行。 需要注意的是,Qt事件循环并不是单线程的。在多线程环境下,每个线程都可以有自己的事件循环,但每个线程只能有一个事件循环。当一个事件需要跨线程传递时,Qt 会通过事件队列和线程间的信号槽机制来实现。 以上是简单的 Qt 事件循环源码分析,如果您对具体的源码细节有更深入的需求,建议参考 Qt 的官方文档和源代码。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

barbyQAQ

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值