简介
每一个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
判断是否可以执行移动动作
- 已经位于目标线程不用移动
threadData的thread其实是指向一个QThread对象;if (d->threadData->thread.loadAcquire() == targetThread) { // object is already in this thread return; }
如果自己已经在目标线程了,那当然啥都不用做了。class QThreadData { ... QAtomicPointer<QThread> thread; ... }
- 有parent的对象不能移动
这个就是这样写的,我暂时不知道为啥,也许是因为处理起来很麻烦。if (d->parent != 0) { qWarning("QObject::moveToThread: Cannot move objects with a parent"); return; }
- UI控件不能移动
我们知道QT是不能在非UI线程创建控件的,所以这个也很好理解,不能将控件移到非UI线程。if (d->isWidget) { qWarning("QObject::moveToThread: Widgets cannot be moved to a new thread"); return; }
三个判断到这里就结束了。下面看看具体的移动动作涉及的几个方面:
执行移动动作
调用moveToThread_helper
- 发送threadChange事件
前面判断完后,会调用
moveToThread_helper很简单,就是发送个事件,然后对子对象递归调用。// prepare to move d->moveToThread_helper();
发送了ThreadChange事件给自己。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(); } }
QObject中对此事件的处理就是释放定时器:
由于处理的是该对象已注册的定时器,所以在这一步用到的threadData得是转移前的线程,所以首先就是发这个消息。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);
这个可以分成2小步: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(); }
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
全部事情都做完后,用到threadData的地方都处理完了,就可以更新threadData了,targetData->ref(); threadData->deref(); threadData = targetData;
ref
增加引用,deref
解引用。
上面执行完后,同moveToThread_helper一样,也会遍历子对象递归调用setThreadData_helper。