多线程中的信号与槽(中)
令人不解的问题:
当槽函数是线程类的成员时,为什么依然不在本线程内被调用执行?
隐藏的问题:
对象依附于哪一个线程?
对象的依附性与槽函数执行的关系?
对象的依附性是否可以改变?
对象依附于哪个线程?
默认情况下,对象依附于自身被创建的线程
例如:对象在主线程(main()函数)中被创建,则依附于主线程
int main(int argc, char* argv[]) { //... TestThread t; //依附于主线程 MyObject m; //依附于主线程 }
对象的依附性与槽函数执行的关系?
默认情况下,槽函数在其所依附的线程中被调用执行
int main(int argc, char* argv[]) { //... TestThread t; //依附于主线程 MyObject m; //依附于主线程 //下面连接中的槽函数都在主线程中被调用执行 QObject::connect(&t,SIGNAL(started()),&m,SLOT(getStarted())); QObject::connect(&t,SIGNAL(testSignal()),&m,SLOT(testSlot())); }
对象的依附性是否可以改变?
QObject::moveToThread用于改变对象的线程的依附性,使得对象的槽函数在依附的线程中被调用执行
int main(int argc, char* argv[]) { //... TestThread t; //依附于主线程 MyObject m; //依附于主线程 //改变对象m的线程的依附性,使其依附于线程t m.moveToThread(&t) }
对象的依附性
MyObject.h
#ifndef MYOBJECT_H #define MYOBJECT_H #include <QObject> class MyObject : public QObject { Q_OBJECT public: MyObject(); protected slots: void getStarted(); void testSlot(); }; #endif // MYOBJECT_H
#ifndef MYOBJECT_H #define MYOBJECT_H #include <QObject> class MyObject : public QObject { Q_OBJECT public: MyObject(); protected slots: void getStarted(); void testSlot(); }; #endif // MYOBJECT_H
TestThread.h
#ifndef TESTTHREAD_H #define TESTTHREAD_H #include <QThread> class TestThread : public QThread { Q_OBJECT protected: void run(); public: TestThread(); signals: void testSignal(); protected slots: void testSlot(); }; #endif // TESTTHREAD_H
#ifndef TESTTHREAD_H #define TESTTHREAD_H #include <QThread> class TestThread : public QThread { Q_OBJECT protected: void run(); public: TestThread(); signals: void testSignal(); protected slots: void testSlot(); }; #endif // TESTTHREAD_H
MyObject.cpp
#include "MyObject.h" #include <QObject> #include <QThread> #include <QDebug> MyObject::MyObject() { } void MyObject::getStarted() { qDebug() <<"void MyObject::getStarted()tid = " << QThread::currentThreadId() ; } void MyObject::testSlot() { qDebug() << "void MyObject::testSlot() " << QThread::currentThreadId() ; }
#include "MyObject.h" #include <QObject> #include <QThread> #include <QDebug> MyObject::MyObject() { } void MyObject::getStarted() { qDebug() <<"void MyObject::getStarted()tid = " << QThread::currentThreadId() ; } void MyObject::testSlot() { qDebug() << "void MyObject::testSlot() " << QThread::currentThreadId() ; }
TestThread.cpp
#include "TestThread.h" #include <QDebug> TestThread::TestThread() { connect(this,SIGNAL(testSignal()),this,SLOT(testSlot())); } void TestThread::run() { qDebug() << "void TestThread::run() begin...tid = " << currentThreadId(); for(int i=0; i<10; i++) { qDebug() << "void TestThread::run() i = " << i; sleep(1); } emit testSignal(); //发射的信号谁来接收呢,可以在构造函数中将信号和槽函数进行关联。 qDebug() << "void TestThread::run() end..."; } void TestThread::testSlot() { qDebug() << "void TestThread::testSlot() tid = " << currentThreadId(); }
#include "TestThread.h" #include <QDebug> TestThread::TestThread() { connect(this,SIGNAL(testSignal()),this,SLOT(testSlot())); } void TestThread::run() { qDebug() << "void TestThread::run() begin...tid = " << currentThreadId(); for(int i=0; i<10; i++) { qDebug() << "void TestThread::run() i = " << i; sleep(1); } emit testSignal(); //发射的信号谁来接收呢,可以在构造函数中将信号和槽函数进行关联。 qDebug() << "void TestThread::run() end..."; } void TestThread::testSlot() { qDebug() << "void TestThread::testSlot() tid = " << currentThreadId(); }
main.cpp
#include <QCoreApplication> #include <QThread> #include <QDebug> #include "TestThread.h" #include "MyObject.h" int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); qDebug() << "main tid() " << QThread::currentThreadId(); TestThread t; MyObject m; QObject::connect(&t,SIGNAL(started()),&m,SLOT(getStarted())); QObject::connect(&t,SIGNAL(testSignal()),&m,SLOT(testSlot())); m.moveToThread(&t); t.start(); return a.exec(); }
#include <QCoreApplication> #include <QThread> #include <QDebug> #include "TestThread.h" #include "MyObject.h" int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); qDebug() << "main tid() " << QThread::currentThreadId(); TestThread t; MyObject m; QObject::connect(&t,SIGNAL(started()),&m,SLOT(getStarted())); QObject::connect(&t,SIGNAL(testSignal()),&m,SLOT(testSlot())); m.moveToThread(&t); t.start(); return a.exec(); }
课程中使用的是Qt4,MyObject相关的槽函数没有被调用,但是使用Qt5.4,MyObject相关的槽函数被调用了。
下面分析没有被调用的情况:
问题:
实验中对象m的槽函数为什么没有全部被执行?
线程中的事件循环
信号与槽的机制需要事件循环的支持
QThread类中提供的exec()函数用于开启线程的事件循环
只有开启事件循环,槽函数才能在信号发送后被调用
信号的发送是随时随地都可以完成的,发送完成后,信号就到事件队列中去了。信号进入事件队列中去有什么用呢?
没人理它,它什么用都没有。如何处理事件队列呢?此时事件循环就派上用场了。
但凡通过exec开启了事件循环,就会不停的从事件队列中取信号,取到信号后就会去判断该信号有没有关联相关的槽函数,
如果有对应的槽函数,则调用相应的槽函数。
想要槽函数在指定的线程中被调用,需要在指定的线程中调用exec函数,开启事件循环。
小结论:
前提条件
对象依附的线程开启了事件循环
后置结果
对象中的槽函数在依附的线程中被调用执行
只需要在TestThread.cpp的run函数中加上exec函数即可
void TestThread::run() { qDebug() << "void TestThread::run() begin...tid = " << currentThreadId(); for(int i=0; i<10; i++) { qDebug() << "void TestThread::run() i = " << i; sleep(1); } emit testSignal(); //发射的信号谁来接收呢,可以在构造函数中将信号和槽函数进行关联。 exec(); qDebug() << "void TestThread::run() end..."; }
void TestThread::run() { qDebug() << "void TestThread::run() begin...tid = " << currentThreadId(); for(int i=0; i<10; i++) { qDebug() << "void TestThread::run() i = " << i; sleep(1); } emit testSignal(); //发射的信号谁来接收呢,可以在构造函数中将信号和槽函数进行关联。 exec(); qDebug() << "void TestThread::run() end..."; }
从打印结果看,与上面没有使用exec的执行结果并无不同,很可能是因为版本不同造成的。
从打印结果看,MyObject的两个槽函数都被调用了,且是在依附于t的那个线程。但是t的槽函数还是依附于主线程,我也想让t的槽函数依附t,怎么操作?非常简单,只需要在main.cpp中的main函数中,加入t.moveToThread(&t)即可,如下所示:
int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); qDebug() << "main tid() " << QThread::currentThreadId(); TestThread t; MyObject m; QObject::connect(&t,SIGNAL(started()),&m,SLOT(getStarted())); QObject::connect(&t,SIGNAL(testSignal()),&m,SLOT(testSlot())); m.moveToThread(&t); t.moveToThread(&t); t.start(); return a.exec(); }
int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); qDebug() << "main tid() " << QThread::currentThreadId(); TestThread t; MyObject m; QObject::connect(&t,SIGNAL(started()),&m,SLOT(getStarted())); QObject::connect(&t,SIGNAL(testSignal()),&m,SLOT(testSlot())); m.moveToThread(&t); t.moveToThread(&t); t.start(); return a.exec(); }
研究槽函数的具体执行线程有什么意义?
当信号的发送与对应槽函数的执行在不同线程中,可能产生临界资源的竞争问题
比如说,在run函数对某一个临界资源进行修改,在槽函数中也对临界资源进行修改,槽函数的调用是在另一个线程中完成的,此时调用槽函数的线程和本身的线程就可能产生竞争问题。