Qt 信号和槽执行线程问题

3 篇文章 0 订阅

Qt 信号和槽执行线程问题

1.QObject的thread() 函数

在Qt中QObject有一个thread() 的函数。这就说明,每一个QOject对象都有所属的线程, 且这个线程是创建这个对象的线程

注意:

  • QThread也是一个QObject的子类,也有自己所在的线程
  • 每个QObject存在父子关系,存在父子关系的两个对象必须在同一个线程中

对于信号和槽的连接,这里涉及到几个对象的thread:

  • sender()
  • receiver()
  • emit 线程

2.信号和槽的连接方式

1. Qt::AutoConnection

Qt::AutoConnection是默认连接类型。如果信号接收方与发送方在同一个线程,它将使用Qt::DirectConnection,否则使用Qt::QueuedConnection。连接类型在信号发射时决定。

这时细心的你 好奇这里连接类型确定为啥不是在连接的时候确定而是在信号发射的时候确定的呢?

2. Qt::DirectConnection

Qt::DirectConnection将导致信号所连接的槽函数立即在发射信号的线程中执行。这意味着槽函数的执行与信号的发射是同步的。如果槽函数执行耗时操作或信号由UI线程发射,可能会导致UI无响应。

3. Qt::QueuedConnection

Qt::QueuedConnection将导致槽函数在接收者所在的线程中执行。这种连接方式下,如果信号被多次触发,相应的槽函数会按照顺序在接收者线程中依次执行。注意:使用QueuedConnection时,参数类型必须是Qt基本类型,或者使用qRegisterMetaType()进行注册的自定义类型。

这也就是对我们说,如果槽函数想在另一个线程执行,把接受者的thread改到另一个线程即可, moveToThread()函数可以做到。

4.Qt::BlockingQueuedConnection

Qt::BlockingQueuedConnection与Qt::QueuedConnection类似,区别在于发送信号的线程在槽函数执行完毕之前会一直处于阻塞状态。因此,发送方和接收方必须处于不同的线程,否则可能导致死锁。

5.Qt::UniqueConnection

Qt::UniqueConnection可以与以上所有连接类型搭配使用。一旦设置了Qt::UniqueConnection,同一信号与同一槽函数的二次连接将会失败,确保了连接的唯一性。

3.信号和槽的执行

(1) 信号执行所在的线程

这个毫无疑问,这个调用emit信号函数的线程。对,信号也就是个函数。

(2)发送者的thread()

这个就是发送者的对象所依附的线程

(3)槽函数执行所造的线程

这个是根据上方连接方式确定的

(4)接受者的thread()

这个就是发送者的对象所依附的线程

显然,我们对于(1)(2)(4)的线程我们很容易确定。唯一不能确定的是槽函数所在的线程。要测试槽函数所在的线程其实很简单,打印线程id就可以了,关键是要想清楚上述几个线程即可,写个代码验证即可!

注意:信号发射的线程可以和发送者的不在一个线程

多读几次上面加粗部分的提示,相信你也可以想明白信号和槽执行的线程问题!

4.简单原理简介

主要原理这里简单介绍一下,下次会有更详细的源码分析。

直连和唯一连接很好实现,主要是队列连接和阻塞连接不好理解。

这里要了解一下QThread和QEventLoop。 QThread中run()的默认实现调用了exec(),从而创建一个QEventLoop对象,由QEventLoop对象处理线程中事件队列(每一个线程都有一个属于自己的事件队列,并且线程安全)中的事件。exec()在其内部不断做着循环遍历事件队列的工作。 QThread默认start是开启一个线程,调用run() 函数,进入事件循环,然后将对象移动到到其他线程后。发射信号,则根据连接类型,进行不同的处理。队列连接是往接受者的线程post一个事件,阻塞连接则是send发送一个事件。(Qt源码中是采用QMetaCallEvent事件间接去触发的,以避免子线程未启动的情况。)

(1)以下是源码:

/*!
    The starting point for the thread. After calling start(), the
    newly created thread calls this function. The default
    implementation simply calls exec().

    You can reimplement this function to facilitate advanced thread
    management. Returning from this method will end the execution of
    the thread.

    \sa start(), wait()
*/
void QThread::run()
{
    (void) exec();
}

/*!
    Enters the event loop and waits until exit() is called, returning the value
    that was passed to exit(). The value returned is 0 if exit() is called via
    quit().

    This function is meant to be called from within run(). It is necessary to
    call this function to start event handling.

    \sa quit(), exit()
*/
int QThread::exec()
{
    Q_D(QThread);
    QMutexLocker locker(&d->mutex);
    d->data->quitNow = false;
    if (d->exited) {
        d->exited = false;
        return d->returnCode;
    }
    locker.unlock();

    QEventLoop eventLoop;
    int returnCode = eventLoop.exec();

    locker.relock();
    d->exited = false;
    d->returnCode = -1;
    return returnCode;
}

(2)activate函数对于线程的判断

 	Qt::HANDLE currentThreadId = QThread::currentThreadId(); // 发送线程id
    bool inSenderThread = currentThreadId == QObjectPrivate::get(sender)->threadData->threadId.loadRelaxed(); // 发送者线程id

    // We need to check against the highest connection id to ensure that signals added
    // during the signal emission are not emitted in this emission.
    uint highestConnectionId = connections->currentConnectionId.loadRelaxed();
    do {
        QObjectPrivate::Connection *c = list->first.loadRelaxed();
        if (!c)
            continue;

        do {
            QObject * const receiver = c->receiver.loadRelaxed();
            if (!receiver)
                continue;

            QThreadData *td = c->receiverThreadData.loadRelaxed(); // 接收者线程id
            if (!td)
                continue;

            bool receiverInSameThread;
            if (inSenderThread) {
                receiverInSameThread = currentThreadId == td->threadId.loadRelaxed();
            } else {
                // need to lock before reading the threadId, because moveToThread() could interfere
                QMutexLocker lock(signalSlotLock(receiver));
                receiverInSameThread = currentThreadId == td->threadId.loadRelaxed();
            }
        }while(..);
     }while(..);

(3)执行槽函数判断

        // 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_index, c, argv);
            continue;
#if QT_CONFIG(thread)
        } else if (c->connectionType == Qt::BlockingQueuedConnection) {
            if (receiverInSameThread) {
                qWarning("Qt: Dead lock detected while activating a BlockingQueuedConnection: "
                "Sender is %s(%p), receiver is %s(%p)",
                sender->metaObject()->className(), sender,
                receiver->metaObject()->className(), receiver);
            }
            QSemaphore semaphore;
            {
                QBasicMutexLocker locker(signalSlotLock(sender));
                if (!c->receiver.loadAcquire())
                    continue;
                QMetaCallEvent *ev = c->isSlotObject ?
                    new QMetaCallEvent(c->slotObj, sender, signal_index, argv, &semaphore) :
                    new QMetaCallEvent(c->method_offset, c->method_relative, c->callFunction,
                                       sender, signal_index, argv, &semaphore);
                QCoreApplication::postEvent(receiver, ev);
            }
            semaphore.acquire();
            continue;
#endif
        }

5.结论

  • 槽函数执行的线程根据接收者所在线程和连接方式
  • 对象的所依附的线程取决于父对象和所创建的线程

注意:

  • 要moveToThread时候千万不要指定父对象,否则移动不过去
  • 移动到的线程必须start才能真正在其线程执行
  • QThread本身也是QObject,指定父对象后自己的thread()不是自己
  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值