`
美丽的小岛
  • 浏览: 309248 次
  • 性别: Icon_minigender_1
  • 来自: 大连
社区版块
存档分类
最新评论

QObject 的线程关联性[转]

    博客分类:
  • QT
 
阅读更多
全文转载自dbzhang800的Bolg,「QObject 之 Thread Affinity」            
  原文地址:http://blog.csdn.net/dbzhang800/article/details/6557272    
  原文发布时间:2011-06-20 22:09                                       

注意,本文试图通过源码解释下面的问题:

  • 子QObject必须在其parent关联的线程内创建
  • 调用moveToThread()的对象其parent必须为0
  • 事件驱动的对象要在单一线程内使用
    • QTimer、network模块的QTcpSocket等等
    • 为什么不能在非关联线程内开启QTimer或者连接QTcpSocket到服务器?
  • 删除QThread对象前,确保线程内所有对象都没销毁
  • AutoConnection的是是非非,两种说法孰是孰非?

    • 其一:信号关联的线程和槽关联的线程不一致时,则Queued方式
    • 其二:信号发射时的当前线程和槽函数关联的线程不一致时,则Queued方式

但很显然,我没做到这一点(能力所限,现阶段我只能让自己勉强明白),尽管如此,本文应该还是提供了很多你理解这些问题所需的背景知识。

QObject的线程关联性

线程关联性(Thread Affinity)???

什么东西?

每一个QObject都会和一个线程相关联

QObject 是线程感知的,每一个QObject及派生类的对象被创建时都会将其所在线程的引用保存下来(可以通过QObject::thread()返回)。

干嘛用的?

用于事件系统

QObject对象的事件处理函数始终要在其所关联线程的上下文中执行。

可否改变?

 

使用QObject::moveToThread()可以将QObject对象从一个线程移动到另一个线程。

QObject

看看QObject的初始化(看两点):

  • 保存当前线程(QThreadData)的指针。
  • 如果其parent关联的线程和当前线程不一致,parent会强制置0。
    • 这要求子对象必须在其parent关联的线程内创建。
    • 当使用QThread时,你不能将QThread对象作为在新线程中所创建的对象的parent。
QObject::QObject(QObject *parent)
    : d_ptr(new QObjectPrivate)
{
    Q_D(QObject);
    d->threadData = (parent && !parent->thread()) ? parent->d_func()->threadData : QThreadData::current();
    if (parent) {
        if (!check_parent_thread(parent, parent ? parent->d_func()->threadData : 0, d->threadData))
            parent = 0;
        setParent(parent);
}

看看moveToThread()的代码(我们此处只关心限制条件):

  • parent非0的对象不能被移动!
  • QWidget及其派生类对象不能被移动!
  • 该函数必须在对象关联的线程内调用!
void QObject::moveToThread(QThread *targetThread)
{
    Q_D(QObject);
    if (d->parent != 0) {
        qWarning("QObject::moveToThread: Cannot move objects with a parent");
        return;
    }
    if (d->isWidget) {
        qWarning("QObject::moveToThread: Widgets cannot be moved to a new thread");
        return;
    }

    QThreadData *currentData = QThreadData::current();
    QThreadData *targetData = targetThread ? QThreadData::get2(targetThread) : new QThreadData(0);
    if (d->threadData->thread == 0 && currentData == targetData) {
        // one exception to the rule: we allow moving objects with no thread affinity to the current thread
        currentData = d->threadData;
    } else if (d->threadData != currentData) {
        qWarning("QObject::moveToThread: Current thread (%p) is not the object's thread (%p)./n"
                 "Cannot move to target thread (%p)/n",
                 currentData->thread, d->threadData->thread, targetData->thread);
        return;
    }
......

moveToThread()的其他工作:

  • 生成并通过sendEvent()派发 QEvent::ThreadChange 事件

  • 解除在当前线程中的timer注册(在目标线程中重新注册)
  • 将该对象在当前事件队列中的事件移动到目标线程的事件队列中
  • ...

事件循环

QCoreApplication::exec()

我们在QDialog 模态对话框与事件循环 一文中提到:

  • 调用的是QEventLoop 的 exec()
int QCoreApplication::exec()
{
...
    QEventLoop eventLoop;
    int returnCode = eventLoop.exec();
...
    return returnCode;
}
  • exec()进而调用 QEventLoop::processEvents()
int QEventLoop::exec(ProcessEventsFlags flags)
{
    Q_D(QEventLoop);
...
    while (!d->exit)
        processEvents(flags | WaitForMoreEvents | EventLoopExec);
...
    return d->returnCode;
}
  • 进而调用本线程内的 eventDispatcher
bool QEventLoop::processEvents(ProcessEventsFlags flags)
{
    Q_D(QEventLoop);
    if (!d->threadData->eventDispatcher)
        return false;
    if (flags & DeferredDeletion)
        QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete);
    return d->threadData->eventDispatcher->processEvents(flags);
}
  • 前面注意这段代码,如果没有eventDispatcher,这个函数什么都不做。这个东西是什么时候创建的呢?
QEventLoop::QEventLoop(QObject *parent)
    : QObject(*new QEventLoopPrivate, parent)
{
    Q_D(QEventLoop);
    if (!d->threadData->eventDispatcher) {
        QThreadPrivate::createEventDispatcher(d->threadData);
    } 
}
  • 一个线程内可以创建并启动多个QEventLoop(事件循环可以嵌套,你经常这样用,只不过可能没意识到,可考虑QEventLoop使用两例 ),而第一个负责创建eventDispatcher.

QCoreApplication::postEvent()

QCoreApplicationn::postEvent()和线程有什么瓜葛?

  • 获取接收者关联的线程信息
  • 将事件放入线程的事件队列
  • 唤醒eventDispatcher
void QCoreApplication::postEvent(QObject *receiver, QEvent *event, int priority)
{
    QThreadData * volatile * pdata = &receiver->d_func()->threadData;
    if (data->postEventList.isEmpty() || data->postEventList.last().priority >= priority) {
        data->postEventList.append(QPostEvent(receiver, event, priority));
    } else {
        QPostEventList::iterator begin = data->postEventList.begin()
                                         + data->postEventList.insertionOffset,
                                   end = data->postEventList.end();
        QPostEventList::iterator at = qUpperBound(begin, end, priority);
        data->postEventList.insert(at, QPostEvent(receiver, event, priority));
    }
    if (data->eventDispatcher)
        data->eventDispatcher->wakeUp();
...

事件派发

无论如何,事件最终都要通过 sendEvent 和 sendSpontaneousEvent 才能派发到接收的对象中

  • send(Spontaneous)Event 直接调用notifyInternal,进而直接调用notify,最终直接调用QObject::event()

  • QObject::event()进而直接调用timerEvent()等事件处理函数
inline bool QCoreApplication::sendEvent(QObject *receiver, QEvent *event)
{  if (event) event->spont = false; return self ? self->notifyInternal(receiver, event) : false; }

inline bool QCoreApplication::sendSpontaneousEvent(QObject *receiver, QEvent *event)
{ if (event) event->spont = true; return self ? self->notifyInternal(receiver, event) : false; }

因为事件由其关联的线程内的eventDispatcher进行派发,所以所有的事件处理函数都会在关联的线程内被调用。如果关联线程的事件循环没有启用呢?就不会有eventispatcher了,timerEvent等事件也就更无从谈起了。

QTimer疑问?

为何只能在其关联的线程内启动timer?

QTimer源码分析(以Windows下实现为例) 一文中,我们谈到:

QTimer的是通过QObject的timerEvent()实现的,开启和关闭定时器是通过QObject的startTimer()和killTimer完成的。

startTimer最终调用对象关联线程的eventDispatcher来注册定时器:

int QObject::startTimer(int interval)
{
    Q_D(QObject);
    return d->threadData->eventDispatcher->registerTimer(interval, this);

在Win32平台下:

void QEventDispatcherWin32::registerTimer(int timerId, int interval, QObject *object)
{
    if (timerId < 1 || interval < 0 || !object) {
        qWarning("QEventDispatcherWin32::registerTimer: invalid arguments");
        return;
    } else if (object->thread() != thread() || thread() != QThread::currentThread()) {
        qWarning("QObject::startTimer: timers cannot be started from another thread");
        return;
    }
...

在Linux平台下:

void QEventDispatcherGlib::registerTimer(int timerId, int interval, QObject *object)
{
#ifndef QT_NO_DEBUG
    if (timerId < 1 || interval < 0 || !object) {
        qWarning("QEventDispatcherGlib::registerTimer: invalid arguments");
        return;
    } else if (object->thread() != thread() || thread() != QThread::currentThread()) {
        qWarning("QObject::startTimer: timers cannot be started from another thread");
        return;
    }
...

在这两个平台下,它都会检查当前线程和dispatcher的线程是否一致。不一致则直接返回。

为什么要这么设计。我不太清楚。或许是因为:注册定时器要用到回调函数,而回调函数需要在注册的线程执行(fix me)。

Qt::AutoConnection

  • 使用connect连接信号槽时,默认是 AutoConnection

  • 使用invokeMethod时,可以指定 AutoConnection

设置AutoConnection就是让Qt帮助我们选择直连还是队列连接的方式。选择的依据就是当前的线程和接收者的关联的线程是否一致,而与信号所在对象关联的线程无关 (对Qt4.8及后续版本,这句话是对的)。

invokeMethod

这个不涉及信号的问题,处理起来很简单:比较当前线程和接收者所关联的线程是否一致即可。

  • 检查Connection的类型,处理AutoConnection

// check connection type 
    QThread *currentThread = QThread::currentThread(); 
    QThread *objectThread = object->thread(); 
    if (connectionType == Qt::AutoConnection) { 
        connectionType = currentThread == objectThread 
                         ? Qt::DirectConnection 
                         : Qt::QueuedConnection; 
    }
  • 对于 直连的,直接调 metacall,它进而去调用对象的 qt_metacall
if (connectionType == Qt::DirectConnection) { 
        return QMetaObject::metacall(object, QMetaObject::InvokeMetaMethod, methodIndex, param) < 0;
  • 对于 Queued 的连接,post 相应的事件,进而转到对象的event()函数中
if (connectionType == Qt::QueuedConnection) { 
            QCoreApplication::postEvent(object, new QMetaCallEvent(methodIndex, 
                                                                   0, 
                                                                   -1, 
                                                                   nargs, 
                                                                   types, 
                                                                   args));

connect

connect中指定了AutoConnection,信号发射时,相应槽是Direct还是Queued方式调用呢???

你应该见过两种说法:

  • 其一:信号关联的线程和槽关联的线程不一致时,则Queued方式
  • 其二:信号发射时的当前线程和槽函数关联的线程不一致时,则Queued方式

注意:在Qt4.7.3(包括)以前,前一种说法是对的(充分条件)。从Qt4.8开始,后面的说法是对的(充要条件)。

看看Qt4.7.3中QMetaObject::activate()的代码:

            // determine if this connection should be sent immediately or
            // put into the event queue
            if ((c->connectionType == Qt::AutoConnection
                 && (currentThreadData != sender->d_func()->threadData
                     || receiver->d_func()->threadData != sender->d_func()->threadData))
                || (c->connectionType == Qt::QueuedConnection)) {
                queued_activate(sender, signal_absolute_index, c, argv ? argv : empty_argv);
                continue;
            }

对比看看Qt4.8中的代码:

            const bool receiverInSameThread = currentThreadId == receiver->d_func()->threadData->threadId;

            // determine if this connection should be sent immediately or
            // put into the event queue
            if ((c->connectionType == Qt::AutoConnection && !receiverInSameThread)
                || (c->connectionType == Qt::QueuedConnection)) {
                queued_activate(sender, signal_absolute_index, c, argv ? argv : empty_argv);
                continue;
            }

参考

  • Qt 源码
分享到:
评论

相关推荐

    Qt5的多线程小程序,继承QObject实现

    当你将一个QObject移动到新的线程时,其所有子对象也会随之移动,确保了线程的一致性。同时,QObject的`thread()`方法可以获取对象当前所在的线程。 2. **线程创建**: 在Qt中,有两种主要的线程创建方式:一是直接...

    Qt 下使用线程绘图

    总结,"Qt下使用线程绘图"涉及的关键技术包括QThread的使用、线程间通信、QObject线程关联以及绘图API的正确使用。通过这些技术,可以实现复杂的图形处理,同时保持用户界面的流畅性。"ThreadImage"这个测试程序就是...

    以文件复制为例将继承QThread、QObject,两种Qt多线程方式作简单对比

    这种方式下,QObject的事件循环会在新线程中运行,保证了信号槽机制的安全性。 总结起来,继承自QThread的方式更直接,但可能导致线程安全问题;而基于QObject的线程池方式更灵活,线程管理更高效,但需要理解更多...

    Qt多线程实现 时间刷新

    2. **QObject和线程关联**:默认情况下,所有QObject对象都在创建它们的线程中运行,但可以通过调用`moveToThread()`函数将其移动到其他线程。 3. **信号与槽**:Qt的信号和槽机制是线程间通信的重要手段。信号可以...

    QT开线程示例

    5. **QObject与线程关联**: 如果需要在新线程中执行一个包含信号和槽的QObject实例,可以使用`moveToThread()`方法将其移动到指定的线程。 ```cpp WorkerObject worker; MyThread thread; worker.moveTo...

    QT的多线程编程示例

    2. **QObject的线程关联**:每个QObject都有一个关联的线程,这个线程负责处理该对象的所有信号和槽。如果想要在不同线程之间通信,必须确保信号和槽的源对象和接收对象都在正确的线程中。 3. **信号和槽**:QT的...

    QThread.rar_QT_QT 多线程_QThread 多线程_qthread_qt多线程

    `QThread`不仅提供了创建和管理线程的基础功能,还允许将对象与线程关联,使得这些对象可以在新的线程上下文中运行,这对于处理I/O密集型或计算密集型任务非常有用。 在`QThread`中,每个`QThread`对象代表一个操作...

    QT多线程moveToThread使用方式

    通过`QObject::connect()`,可以设置信号和槽的线程关联,确保数据交换的正确性。 7. **线程生命周期管理**:不要忘记在不再需要线程时,正确地结束和销毁线程。可以使用`quit()`方法来停止事件循环,然后等待`wait...

    Qt中线程跟信号槽的关系

    Qt内部采用了一种称为"线程绑定"(Thread Affinity)的概念,每个QObject都有一个关联的线程。默认情况下,创建的QObject与创建它的线程绑定。当在不同的线程中触发信号或调用槽函数时,Qt会自动处理线程同步,避免...

    qt编程_在子线程中更新UI界面

    6. **更新UI的正确方式**:使用`QObject::invokeMethod()`、`QMetaObject::invokeMethod()`或`Qt::QueuedConnection`类型的信号槽连接来确保UI更新在正确的线程中执行。例如,可以创建一个信号,当子线程完成任务时...

    QT线程QThread的推荐用法

    另外,QThread还可以与QObject直接关联,让对象在新线程中运行。这种方式适用于需要长时间运行的QObject子类实例,例如数据库连接或网络请求。以下是一个例子: ```cpp class Worker : public QObject { Q_OBJECT ...

    qt多线程简单

    在Qt中,线程的生命周期与QThread对象关联,因此通常在`finished`信号触发时删除QThread对象,以避免内存泄漏。 七、线程优先级 Qt并不直接提供设置线程优先级的接口,因为这通常取决于操作系统的实现。不过,可以...

    qt推荐线程用法

    当工作完成时,线程会自动退出,所有关联的对象也会被正确地销毁。 总结,Qt推荐的多线程使用方式是通过继承`QObject`,利用其信号和槽机制来实现线程间的通信,而不是直接重写`QThread`的`run()`方法。这种方法更...

    串口单独一个线程

    然而,更推荐的方式是使用信号和槽机制,将工作线程与`QObject`关联,这样可以更好地管理线程生命周期和资源。这正是`moveToThread(&thread)`方法的作用,它将对象移动到指定的线程中执行,确保其所有后续的槽函数都...

    Qt主线程与次线程通信实例代码

    同时,可以使用moveToThread()函数将QObject移动到指定的线程,以便在该线程中执行其槽函数。 3. **异步事件**:除了信号和槽,Qt还允许通过postEvent()函数发送自定义事件,从而实现线程间通信。这种方式适用于不...

    Qtcpserver实现多线程服务器,里面包括单线程普通服务器

    多线程服务器能够有效地提升服务器的并发能力,但同时也增加了编程复杂性,需要考虑线程同步、资源管理等问题。而在选择单线程还是多线程模式时,应根据具体应用场景的并发需求和资源限制来决定。 在提供的文件列表...

    Qt自定义事件,Qt线程应用

    3. **信号和槽跨线程通信**:Qt的信号槽机制支持跨线程通信,只要信号和槽的接收者位于同一线程,或者它们都与一个线程关联。 4. **线程同步**:可以使用`QMutex`、`QWaitCondition`等同步原语来避免线程间的竞态...

    026 QWidget类分析显示和隐藏接口说明线程类QThread使用方法.7z

    2. 将需要在新线程中运行的类(通常是继承自`QObject`的类)的实例与`QThread`关联起来,通常是通过设置`moveToThread()`方法。 3. 在新的线程中启动工作,这可能涉及到重写`run()`方法。 4. 实现线程间的通信,可以...

    Qt中的两种线程启动运行方式

    `QRunnable`是一个不带线程关联的类,可以被用来包装可运行的任务。`QtConcurrent::run()`函数可以在任意线程池中的线程上执行任务,而无需创建新的QThread对象。例如: ```cpp #include class MyRunnable : ...

Global site tag (gtag.js) - Google Analytics