对QThread的run函数描述如下:
- The run() implementation is for a thread what the main() entry point is for the application. All code executed in a call stack that starts in the run() function is executed by the new thread, and the thread finishes when the function returns.
Bradley T. Hughes 给出说明是: QThread 应该被看做是操作系统线程的接口或控制点,而不应该包含需要在新线程中运行的代码。需要运行的代码应该放到一个QObject的子类中,然后将该子类的对象moveToThread到新线程中。
QObject::connect的三种连接方式定义:
- 自动连接
- 这是默认设置
- 如果信号在接收者所依附的线程内发射,则等同于直接连接
- 如果发射信号的线程和接收者所依附的线程不同,则等同于队列连接
- 也就是说只存在下面两种情况:直接连接、队列连接
- 直接连接
- 当信号发射时,槽函数将直接被调用
- 无论槽函数所属对象在哪个线程,槽函数都在发射信号的线程内执行(根据该条描述,自动连接的2)是特例,正好发射信号的线程和槽函数所依附的线程是同一线程,所以也在接收者所依附的线程执行。其他情况下直接连接并不一定是在接收者所依附的线程执行)
- 队列连接
- 当控制权回到接收者所依附线程的事件循环时,槽函数被调用
- 槽函数在接收者所依附的线程执行
关于QThread的几个结论:
- QThread是用来管理线程的,它所依附的线程和它管理的线程不是同一个东西;
- QThread所依附的线程是执行QThread t(0)或QThread* pT=new QThread(0)的线程;
- QThread管理的线程,就是run启动的线程
看下面的例子:
#include <QCoreApplication>
#include <QObject>
#include <QThread>
#include <QDebug>
class Dummy:public QObject
{
Q_OBJECT
public:
Dummy(){}
public slots: //槽函数
void emitsignal()
{
emit sig();
}
signals: //信号
void sig();
};
class Thread:public QThread
{
Q_OBJECT
public:
Thread(QObject* parent=0):QThread (parent)
{
//moveToThread(this);
}
public slots:
void slot_main()
{
qDebug()<<"Thread slot_main:"<<currentThreadId();
}
protected:
void run()
{
qDebug()<<"Thread run:"<<currentThreadId();
exec();
}
};
#include "main.moc"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
qDebug()<<"main Thread:"<<QThread::currentThreadId();
Thread thread;
Dummy dummy;
QObject::connect(&dummy,SIGNAL(sig()),&thread,SLOT(slot_main())); //自动连接的等同于直接连接的情况,槽函数在主线程中被调用
thread.start();
dummy.emitsignal();
return a.exec();
}
执行结果:
从结果可以看出:槽函数的线程和主线程是一样的。
Qt自带的例子QThread中slot和run函数共同操作的对象,都会用QMutex锁住。为什么?
因为slot和run处于不同的线程,需要线程间的同步!
如果想让槽函数slot在次线程中运行(比如它执行耗时的操作会让主线程卡住或死掉),怎么解决呢?
- 注意:例子中是自动连接中等同于直接连接的情况,dummy信号发射的线程和接收者QThread所依附的线程相同,都是主线程。所以槽函数会在主线程中被调用。
- 参考前面的结论:
将thread依附的线程改为次线程,即自动连接中等同于队列连接的情况,槽函数在thread所依附的线程执行;
这也是代码中Thread的构造函数中moveToThread(this);所做的,去掉注释后运行结果如下:
Slot确实在thread中被执行,但这是Bradley T. Hughes强烈批判的用法。
run中信号与QThread中槽
定义一个Dummy类,在run中发射它的信号,在主线程中实例化Thread
也可以在run中发射Thread类中的信号,而不是Dummy(效果完全一样)
这是自动连接中等同于队列连接的情况,thread本身依附的线程是主线程,所以它的槽函数也在主线程执行。
如何解决呢(在主线程执行的问题)?
- (方法一)前面提到了moveToThread可以用也可以解决问题。但同样是被批判的对象。
- (方法二)注意,这里信号是次线程发出的,对比connect的连接方式,会发现:
- 采用直接连接,槽函数将在次线程(信号发出的线程)执行
- 这个方法不太好,因为你需要处理slot和它的对象所在线程的同步。需要QMutex一类的东西slot在次线程执行,它的对象所在线程是thread所依附的线程即主线程,也就是说需要处理主线程和次线程的同步
推荐的方法
这是一个又简单又好用的方法。定义一个普通的QObject派生类,然后将其对象move到QThread中。使用信号和槽时根本不用考虑多线程的存在。也不用使用QMutex来进行同步,Qt的事件循环会自己自动处理好这个。(没太明白???)
代码示例:
#include <QCoreApplication>
#include <QObject>
#include <QThread>
#include <QDebug>
class Dummy:public QObject
{
Q_OBJECT
public:
Dummy(){}
public:
void emitsignal()
{
emit sig();
}
signals: //信号
void sig();
};
class Object : public QObject
{
Q_OBJECT
public:
Object() {}
virtual ~Object() {}
public slots:
void slot()
{
qDebug()<<"thread slot:"<<QThread::currentThreadId();
}
};
#include "main.moc"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
qDebug()<<"main Thread:"<<QThread::currentThreadId();
QThread thread;
Object obj;
Dummy dummy;
obj.moveToThread(&thread);
QObject::connect(&dummy,SIGNAL(sig()),&obj,SLOT(slot()));
thread.start();
dummy.emitsignal();
return a.exec();
}
执行结果:
Slot确实不在主线程中执行了