QT moveToThread解析

简介

每一个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;
    }
    
    threadData的thread其实是指向一个QThread对象;
    class QThreadData
    {
    	...
    	QAtomicPointer<QThread> thread;
    	...
    }
    
    如果自己已经在目标线程了,那当然啥都不用做了。
  2. 有parent的对象不能移动
    if (d->parent != 0) {
        qWarning("QObject::moveToThread: Cannot move objects with a parent");
        return;
    }
    
    这个就是这样写的,我暂时不知道为啥,也许是因为处理起来很麻烦。
  3. UI控件不能移动
    if (d->isWidget) {
        qWarning("QObject::moveToThread: Widgets cannot be moved to a new thread");
        return;
    }
    
    我们知道QT是不能在非UI线程创建控件的,所以这个也很好理解,不能将控件移到非UI线程。
    三个判断到这里就结束了。下面看看具体的移动动作涉及的几个方面:

执行移动动作

调用moveToThread_helper

  1. 发送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;
    }
    
    由于处理的是该对象已注册的定时器,所以在这一步用到的threadData得是转移前的线程,所以首先就是发这个消息。

调用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();
    }
    
    这个可以分成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;
            }
        }
    
    }
    
    这块儿代码比较长,暂时还没想明白,后面想明白了再来更新,有大佬知道的话也可以说下。
  3. 修改threadData
    targetData->ref();
    threadData->deref();
    threadData = targetData;
    
    全部事情都做完后,用到threadData的地方都处理完了,就可以更新threadData了,ref增加引用,deref解引用。
    上面执行完后,同moveToThread_helper一样,也会遍历子对象递归调用setThreadData_helper。
  • 1
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值