目录
- 简介
- 源码分析
- 判断是否可以执行移动动作
- 执行移动动作
- 调用moveToThread_helper
- 调用setThreadData_helper
简介
每一个QObject子类都会关联到一个具体QThread线程上,QObject有一个QThreadObject数据成员,该成员在Qobject构造时关联到具体的线程上:
class Q_CORE_EXPORT QObjectPrivate : public QObjectData
{
...
QThreadData *threadData; // id of the thread that owns the object
...
}
QObject::QObject(QObjectPrivate &dd, QObject *parent)
: d_ptr(&dd)
{
...
d->threadData = (parent && !parent->thread()) ? parent->d_func()->threadData : QThreadData::current();
d->threadData->ref();
...
}
可以看到,其实对象在构造时,就会与具体的线程关联起来:
1)如果有parent且parent已经关联到具体线程,则会直接关联到parent所在的线程;
2)否则,关联到当前线程。
而moveToThread用于将一个QObject子类对象移到另一个线程,常见于多线程编程中。
源码分析
movetoThread主要分两部分:
- 判断是否可以执行移动动作
1.1 已经位于目标线程不用移动
1.2 有parent的对象不能移动
1.3 UI控件不能移动 - 执行移动动作
2.1 发送threadChange事件
2.2 处理消息队列中消息receiver为自己的消息
2.3 处理自己的connection
2.4 修改threadData
判断是否可以执行移动动作
- 已经位于目标线程不用移动
if (d->threadData->thread.loadAcquire() == targetThread) {
// object is already in this thread
return;
}
- threadData的thread其实是指向一个QThread对象;
class QThreadData
{
...
QAtomicPointer<QThread> thread;
...
}
- 有parent的对象不能移动
if (d->parent != 0) {
qWarning("QObject::moveToThread: Cannot move objects with a parent");
return;
}
- UI控件不能移动
if (d->isWidget) {
qWarning("QObject::moveToThread: Widgets cannot be moved to a new thread");
return;
}
- 我们知道QT是不能在非UI线程创建控件的,所以这个也很好理解,不能将控件移到非UI线程。
三个判断到这里就结束了。下面看看具体的移动动作涉及的几个方面:
执行移动动作
调用moveToThread_helper
- 发送threadChange事件
前面判断完后,会调用
// prepare to move
d->moveToThread_helper();
- moveToThread_helper很简单,就是发送个事件,然后对子对象递归调用。
void QObjectPrivate::moveToThread_helper()
{
Q_Q(QObject);
QEvent e(QEvent::ThreadChange);
QCoreApplication::sendEvent(q, &e);
for (int i = 0; i < children.size(); ++i) {
QObject *child = children.at(i);
child->d_func()->moveToThread_helper();
}
}
- 发送了ThreadChange事件给自己。
QObject中对此事件的处理就是释放定时器:
case QEvent::ThreadChange: {
Q_D(QObject);
QThreadData *threadData = d->threadData;
QAbstractEventDispatcher *eventDispatcher = threadData->eventDispatcher.loadRelaxed();
if (eventDispatcher) {
QList<QAbstractEventDispatcher::TimerInfo> timers = eventDispatcher->registeredTimers(this);
if (!timers.isEmpty()) {
// do not to release our timer ids back to the pool (since the timer ids are moving to a new thread).
eventDispatcher->unregisterTimers(this);
QMetaObject::invokeMethod(this, "_q_reregisterTimers", Qt::QueuedConnection,
Q_ARG(void*, (new QList<QAbstractEventDispatcher::TimerInfo>(timers))));
}
}
break;
}
调用setThreadData_helper
- 处理消息队列中消息receiver为自己的消息
调用moveToThread_helper完后,之后的几个步骤都是setThreadData_helper调用里的:
if (!targetData)
targetData = new QThreadData(0);
currentData->ref();
// move the object
d_func()->setThreadData_helper(currentData, targetData);
int eventsMoved = 0;
for (int i = 0; i < currentData->postEventList.size(); ++i) {
const QPostEvent &pe = currentData->postEventList.at(i);
if (!pe.event)
continue;
if (pe.receiver == q) {
// move this post event to the targetList
targetData->postEventList.addEvent(pe);
const_cast<QPostEvent &>(pe).event = 0;
++eventsMoved;
}
}
if (eventsMoved > 0 && targetData->hasEventDispatcher()) {
targetData->canWait = false;
targetData->eventDispatcher.loadRelaxed()->wakeUp();
}
- 这个可以分成2小步:
1)遍历对象所处当前线程中所有事件,将receiver为自己的事件全部移到(不是复制)新线程的消息队列里。
2)如果真的有事件被移动,则尝试对目标线程调用wakeUp,告诉线程可以起来工作了。 - 处理自己的connection
ConnectionData *cd = connections.loadRelaxed();
if (cd) {
if (cd->currentSender) {
cd->currentSender->receiverDeleted();
cd->currentSender = nullptr;
}
// adjust the receiverThreadId values in the Connections
if (cd) {
auto *c = cd->senders;
while (c) {
QObject *r = c->receiver.loadRelaxed();
if (r) {
Q_ASSERT(r == q);
targetData->ref();
QThreadData *old = c->receiverThreadData.loadRelaxed();
if (old)
old->deref();
c->receiverThreadData.storeRelaxed(targetData);
}
c = c->next;
}
}
}
- 修改threadData
targetData->ref();
threadData->deref();
threadData = targetData;
- 全部事情都做完后,用到threadData的地方都处理完了,就可以更新threadData了,
ref
增加引用,deref
解引用。
上面执行完后,同moveToThread_helper一样,也会遍历子对象递归调用setThreadData_helper。