1.QThread类
QThread类并不是代表一个新的线程,而是QT提供的一个接口,用于控制一个子线程。每个QThread的实例就代表着对一个新线程的一个控制类。对于第一次使用QT多线程的人,或许就会很迷惑很不适应。
QThread提供一个公共槽接口--start(),当你有一个QThread的实例例如
QThread q_thread
当你调用q_thread.start()时,q_thread控制的子线程就会开始执行,执行的入口就是QThread类的virtual protected 函数 QThread::run(),此时,run()里面的语句都是在新起的一个线程里执行,默认QT自带的run()实现就是QThread::exec(),表示开始QThread类的消息循环。exec()执行后,q_thread就能接受到信号(如果有连接信号的话),执行响应的槽。
重点来了,run()里面的语句是在新起的线程执行的,而q_thread收到信号,执行相应槽里面的语句,却是由QThread所在的线程执行的。包括继承QThread类的子类以及其信号槽。例如
一个QThread的子类:
class MyThread : public QThread
{
Q_OBJECT
public:
MyThread();
protected:
virtual void run ();
public slots:
void MyThreadSlot();
signals:
void MyThreadSignal();
};
void MyThread::run()
{
int i = 0;
i++;
exec();
}
一个主对话框类,里面声明一个MyThread变量
class MainWindow : public QMainWindow {
Q_OBJECT
public:
MainWindow(QWidget *parent = 0);
private:
MyThread q_myThread;
public slots:
void MainWindowSlot();
signals:
void MainWindowSignal();
};
当我们在MainWindow里调用q_myThread.start()时, MyThread::run()开始执行,假设主线程是Thread1,新起的线程是Thread2,
int i = 0;i++;都是在Thread2里执行。接着exec()后,MyThread的事件循环开始了。
我们先
connect(this,SIGNAL(MainWindowSigna()),&q_myThread,SLOT(MyThreadSlot()),Qt::QueuedConnection);
当MainWindow里 emit MainWindowSignal()时,MyThread的MyThreadSlot()会执行,你会发现,MyThreadSlot()执行的线程并不是我们想的Thread2,而是Thread1,因为q_thread在MainWindow中,MainWindow就在主线程Thread1中。那么我们如何才能在让我们写的代码在Thread2执行呢,其中一个方法便是改写run(),函数。
void MyThread::run()
{
while(1)
{
do_something();
}
}
当我们调用q_myThread.start()时,do_something()就会循环执行,并且在新的线程Thread2中。而MainWindow的消息循环继续在Thread1里执行。总算有点像Windows的API CreateThread()了,但是如何给他传参数,在MyThread()的参数表里加?可以,那么如何与Thread1同步?在参数里加个用于同步的锁的地址?这样两个类之间的耦合度又大大增加,MyThread类几乎是不可重用的了。以前在Windows写多线程,最多就写个函数和它的参数的结构体,现在在QT还得子类化QThread,而且得到一个结构奇差的多线程结构。可真麻烦。
既然使用了QT,当然好好利用QT的信号槽机制,使得各类设计上比较独立,提高可重用性。一个更好的方法是利用QObject::moveToThread(QThread*)函数。前面我们说过,QThread的槽函数会在QThread类所在的线程执行,所以我们不应该子类化QThread,因为子类化之后它的槽函数依然会在MainWindow所在线程执行,往往不是我们所想要的。除非你想进入改写run()函数这个恶梦。
我们声明一个新的类,继承QObject,因为QOject可以使用信号槽。更重要的因为moveToThread()就是QObject的成员函数。
class MyObject : public QObject
{
Q_OBJECT
public:
MyObject();
public slots:
void MyObjectSlot();
signals:
void MyObjectSignal();
};
在MainWindow类中声明 MyObject q_myObj;
在构造函数里做如下处理
connect(this,SIGNAL(MainWindowSignal()),&q_myObj,SLOT(MyObjectSlot()),Qt::QueuedConnection);
q_myObj.moveToThread(&q_myThread);
q_myThread.start();
当我们发送一个信号MainWindowSignal()信号时,发现MyObjectSlot()是在新线程Thread2里执行的,不妨在MyObjectSlot()里sleep1分钟,MainWindow还是能响应的。
至于MyObject的设计就随个人喜好了。可以把需要用到新线程的工作封装到MyObject的槽函数里,并且可以通过信号来通知MyObject开始或停止工作,而且还可以传参数。设计上比重载run()要好得多。
总结,要使用多线程,先继承QObject,在里面实现需要多线程运行的语句。然后在主线程里声明一个QThread对象,把自己实现的Obj moveToThread移动到新线程里, QThread::start()开始运行。