目录

  • 简介
  • 源码分析
  • 判断是否可以执行移动动作
  • 执行移动动作
  • 调用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 已经位于目标线程不用移动
    1.2 有parent的对象不能移动
    1.3 UI控件不能移动
  2. 执行移动动作
    2.1 发送threadChange事件
    2.2 处理消息队列中消息receiver为自己的消息
    2.3 处理自己的connection
    2.4 修改threadData

判断是否可以执行移动动作

  1. 已经位于目标线程不用移动
if (d->threadData->thread.loadAcquire() == targetThread) {
    // object is already in this thread
    return;
}
  1. threadData的thread其实是指向一个QThread对象;
class QThreadData
{
	...
	QAtomicPointer<QThread> thread;
	...
}
  1. 有parent的对象不能移动
if (d->parent != 0) {
    qWarning("QObject::moveToThread: Cannot move objects with a parent");
    return;
}
  1. UI控件不能移动
if (d->isWidget) {
    qWarning("QObject::moveToThread: Widgets cannot be moved to a new thread");
    return;
}
  1. 我们知道QT是不能在非UI线程创建控件的,所以这个也很好理解,不能将控件移到非UI线程。
    三个判断到这里就结束了。下面看看具体的移动动作涉及的几个方面:

执行移动动作

调用moveToThread_helper

  1. 发送threadChange事件
    前面判断完后,会调用
// prepare to move
d->moveToThread_helper();
  1. 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();
   }
}
  1. 发送了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

  1. 处理消息队列中消息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();
}
  1. 这个可以分成2小步:
    1)遍历对象所处当前线程中所有事件,将receiver为自己的事件全部移到(不是复制)新线程的消息队列里。
    2)如果真的有事件被移动,则尝试对目标线程调用wakeUp,告诉线程可以起来工作了。
  2. 处理自己的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;
        }
    }

}
  1. 修改threadData
targetData->ref();
threadData->deref();
threadData = targetData;
  1. 全部事情都做完后,用到threadData的地方都处理完了,就可以更新threadData了,ref增加引用,deref解引用。
    上面执行完后,同moveToThread_helper一样,也会遍历子对象递归调用setThreadData_helper。