Qt学习之路—–使用QObject来创建线程

August 6th, 2013 by JasonLe's Tech Leave a reply »

QT首先为我们提供了信号和槽的机制,且该机制原生支持跨线程。假设我们在16核心服务器上,则使用 15个 QThread对象管理15组工作线程(留一个给主界面)。但是,如果仔细看了QT的文档,就会发现QThread的信号事件循环默认是在创建者中(很多时候就是主线程!),所以,要想让槽在子线程运行,一般是派生一个QObject的类,并把对象MoveToThread到某个QThread管理的线程上去。这样,信号和槽就是全异步FIFO了。其次,QT提供了引用计数的QByteArray封装,这个东西在参数传递的时候,速度很快,很少出现memcpy,生存期也特别容易控制。虽然C++11里有 shared_ptr<T>,但是那个东西还是需要在一开始new 一个int8型的存储区,很讨厌。

之前我参考了一些代码,都是在主线程中声明QThread后,然后直接调用start()函数,这样的话根本没有起到多线程的功能,因为都是将QThread类化以后,在主线程中执行的。要想将费事的操作另外开一个线程。可以使用QObject对象,然后使用moveToThread(),最后使用类化的QObject的对象调用start()方法就可以了。

>QThread mthread;
myobject object;//myobject继承QThread
object.DoWork(mthread);
object.moveToThread(&amp;mthread);

mthread.start();

.cpp

void myobject::DoWork(QThread &amp;thread)//关联线程与SLOT函数,thread一启动,就执行SLOT函数。
{
connect(&amp;thread,SIGNAL(started()),this,SLOT(dododo()));
}
void myobject::dododo()//SLOT 函数
{
for(int i = 0 ;i&lt;10;i++)
{
qDebug() &lt;&lt; i;
}
}

参考以下文章编写:

http://blog.csdn.net/imhikaru/article/details/6635095

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()),&amp;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);

当我们发送一个信号MainWindowSignal()信号时,发现MyObjectSlot()是在新线程Thread2里执行的,不妨在MyObjectSlot()里sleep1分钟,MainWindow还是能响应的。

至于MyObject的设计就随个人喜好了。可以把需要用到新线程的工作封装到MyObject的槽函数里,并且可以通过信号来通知MyObject开始或停止工作,而且还可以传参数。设计上比重载run()要好得多。

总结,要使用多线程,先继承QObject,在里面实现需要多线程运行的语句。然后在主线程里声明一个QThread对象,把自己实现的Obj moveToThread移动到新线程里, QThread::start()开始运行。