文章目录
- 一、 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
继承自类(模板)QBasicAtomicPointer
,QBasicAtomicPointer
组合了变量QAtomicOps
,QAtomicOps
相当于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的事件的。
事件的发送有send
和post
两种机制,send
的事件会立刻执行,post
的则需要放到队列中,靠事件循环去推动。所以并没有看到sendEventList
, 灰常合理。
同时里面还有:
- 线程
thread
- 线程id
threadId
- 事件分发器
eventDispatcher
(底层实现与平台相关)。 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;}
析构时将执行了逆操作:
QEventLoop *eventLoop = threadData->eventLoops.pop();
- 从线程数据中弹出一个时间循环。
d->inExec = false;
- 将该事件循环标记为未在执行。
--threadData->loopLevel;
- 递减事件循环层深。
4.4 小结
至此,局部类LoopReference
的作用就搞清楚了,
LoopReference ref(d, locker);
上面这行代码的作用就是按照RAII原则,
在构造时:
- 将传入的事件循环标记为在执行中。
- 将事件循环压放入线程数据(
threadData
)的事件循环栈中。 - 将线程数据(
threadData
)中的循环层深loopLevel
加1。
在析构时执行逆操作:
- 将传入的事件循环标记为未在执行。
- 从线程数据(
threadData
)的事件循环栈中弹出一个事件循环。 - 将线程数据(
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->exit
为true
时才会退出循环。否则就继续调用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);
}
可见其中也是调用了事件循环所属线程的eventDispatcher
的processEvents()
。
eventDispatcher
的类型如下:
QAtomicPointer<QAbstractEventDispatcher> eventDispatcher;
事件分发器的实现与平台相关,这一块的实现会再开一篇。
三、小结
- Qt一般将类分为公开部分和隐藏部分,例如
QWidget
和QWidgetPrivate
。 - Qt中宏
Q_D
用于在公开类访问隐藏实现部分(获取d
指针),Q_Q
用于在隐藏实现部分访问公开部分(获取q
指针)。 - 每个
QObject
背后都存有一个QObjectData
指针,在构造时用QObjectData *
为其赋值;QObjectPrivate
继承自QObejctData
,其中存有成员变量QThreadData
,记录了线程数据。 - 事件循环
QEventLoop
继承自QObject
,所以每个事件循环都有所属线程。(QCoreApplication
和其他类同理) - 线程数据
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