在Qt中QObject有一个thread() 的函数。这就说明,每一个QOject对象都有所属的线程。
注意:
对于信号和槽的连接,这里涉及到几个对象的thread:
Qt::AutoConnection是默认连接类型。如果信号接收方与发送方在同一个线程,它将使用Qt::DirectConnection,否则使用Qt::QueuedConnection。连接类型在信号发射时决定。
这时细心的你 好奇这里连接类型确定为啥不是在连接的时候确定而是在信号发射的时候确定的呢?
Qt::DirectConnection将导致信号所连接的槽函数立即在发射信号的线程中执行。这意味着槽函数的执行与信号的发射是同步的。如果槽函数执行耗时操作或信号由UI线程发射,可能会导致UI无响应。
Qt::QueuedConnection将导致槽函数在接收者所在的线程中执行。这种连接方式下,如果信号被多次触发,相应的槽函数会按照顺序在接收者线程中依次执行。注意:使用QueuedConnection时,参数类型必须是Qt基本类型,或者使用qRegisterMetaType()进行注册的自定义类型。
这也就是对我们说,如果槽函数想在另一个线程执行,把接受者的thread改到另一个线程即可, moveToThread()函数可以做到。
Qt::BlockingQueuedConnection与Qt::QueuedConnection类似,区别在于发送信号的线程在槽函数执行完毕之前会一直处于阻塞状态。因此,发送方和接收方必须处于不同的线程,否则可能导致死锁。
Qt::UniqueConnection可以与以上所有连接类型搭配使用。一旦设置了Qt::UniqueConnection,同一信号与同一槽函数的二次连接将会失败,确保了连接的唯一性。
(1) 信号执行所在的线程
这个毫无疑问,这个调用emit信号函数的线程。对,信号也就是个函数。
(2)发送者的thread()
这个就是发送者的对象所依附的线程
(3)槽函数执行所造的线程
这个是根据上方连接方式确定的
(4)接受者的thread()
这个就是发送者的对象所依附的线程
显然,我们对于(1)(2)(4)的线程我们很容易确定。唯一不能确定的是槽函数所在的线程。要测试槽函数所在的线程其实很简单,打印线程id就可以了,关键是要想清楚上述几个线程即可,写个代码验证即可!
注意:信号发射的线程可以和发送者的不在一个线程
多读几次上面加粗部分的提示,相信你也可以想明白信号和槽执行的线程问题!
主要原理这里简单介绍一下,下次会有更详细的源码分析。
直连和唯一连接很好实现,主要是队列连接和阻塞连接不好理解。
这里要了解一下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
}
注意: