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
    评论
### 回答1: Qt中的moveToThread函数主要用于将一个QObject对象从当前线程移动到另一个线程中执行。当我们在Qt中使用多线程时,如果某个对象在不同线程间执行任务,这个对象需要通过moveToThread来手动地将自己移动到目标线程中,以确保线程安全。 moveToThread函数的用法很简单,只需将目标线程的QThread对象作为参数传入即可,例如: ```cpp QThread* thread = new QThread; QObject* obj = new MyObject; // MyObject为继承QObject的类 obj->moveToThread(thread); ``` 此时,obj对象就会被移到thread线程中执行。 需要注意的是,moveToThread仅能将QObject对象移动到新线程中执行,而不能将QWidget对象移动,因为QWidget依赖于窗口系统,只能在创建它的线程中使用,否则可能会导致程序异常。 当对象通过moveToThread移动到新线程中时,需要确保该对象所在线程内没有任何活动,否则可能会导致无法正确退出线程。通常,我们可以在目标线程中使用一个槽函数来执行具体任务,然后通过信号和槽机制将任务传递给对象,让它在目标线程中执行。 总的来说,QtmoveToThread函数可以方便地将QObject对象从一个线程移动到另一个线程中执行任务,从而实现多线程编程中的任务分发和线程间的数据共享。 ### 回答2: QtMoveToThread是一种线程间通信机制。在Qt框架中,每个对象都属于某个特定的线程,每个线程具有自己的事件循环。然而,在某些情况下,我们可能需要将一个对象从一个线程移动到另一个线程,以便在不同的线程中执行。 MoveToThread可以用于创建新的线程或将对象从一个线程移动到另一个线程。首先,我们需要实例化一个QThread对象,作为一个新的线程。然后,我们可以使用MoveToThread将一个QObject对象移动到这个线程,并将其执行的槽函数与相应的信号连接起来。这样,当信号被触发时,槽函数将在新线程中执行,实现了线程间的通信。 在使用MoveToThread时,需要注意一些事项。首先,QObject对象必须位于堆上,而不是栈上,因为它将被转移所有权。其次,该对象的线程属性必须是非主线程,即不能是QCoreApplication::instance()->thread()。最后,当对象被移动到新线程时,它的事件循环将会在新线程中运行,所以我们需要在新线程中重新实现事件处理函数。 使用MoveToThread可以使我们实现复杂的多线程应用程序更加轻松。例如,在一个图像处理应用程序中,我们可以将图像加载和处理的部分放在一个单独的线程中执行,以确保主线程不被阻塞,并提高应用程序的响应性能。 总之,QtMoveToThread提供了一种方便的方式来实现线程间的通信,使我们能够更好地利用多核处理器,并提高应用程序的性能和稳定性。 ### 回答3: Qt中的`moveToThread`方法是用来将一个对象移动到指定的线程中执行的。 在多线程编程中,Qt的`QThread`类提供了基本的线程功能,但是为了保证线程安全,Qt规定只有在创建一个对象时,该对象所属的线程就被确定了,后续不能再更改所属线程。这就意味着对象的成员函数只能在创建它的线程中被调用。 然而,有时候我们需要将一个对象的执行过程放在不同的线程中进行,这就需要使用到`moveToThread`方法。该方法的使用非常简单,只需要将需要移动的对象调用`moveToThread`方法并传入目标线程指针即可。 使用`moveToThread`方法后,原本属于对象的线程变为目标线程,所以对象的成员函数就可以在目标线程中被调用了。当我们调用`moveToThread`方法时,Qt会自动创建一个新的线程并将对象移动到该线程中,并且自动启动该线程。 需要注意的是,`moveToThread`方法只是将一个对象置于某个线程中执行,但并不会自动处理线程间的消息或事件循环。如果需要在移动后的线程中处理消息,还需要在目标线程中创建消息循环,以接收、处理消息和事件。 总之,`moveToThread`方法是Qt提供的一个方便的方法,可以将一个对象移动到指定的线程中执行,使得对象的成员函数能够在目标线程中被调用。但是需要注意处理好线程间的消息通信和事件循环。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值