QThread学习

工作需要,前段时间要写TCP连接的模块,因为连接本身可能是一个耗时的操作,而且要实现自动重连功能,所以必须要用到多线程,因此有机会学习QThread,现将体会成文。

首先是QThread::exec(),这个函数将进入当前线程的事件循环(网上很多文章都写成时间循环),调用这个函数后将会阻塞线程,这时线程中的事件传递才有效,信号与槽的连接才有效,此时信号发射后槽函数才会被调用。当执行了线程的quit()或exit()函数时exec()函数才返回。

看程序

//mythread.h
#ifndef MYTHREAD_H
#define MYTHREAD_H
#include <QThread>
class QTimer ;
class MyThread : public QThread
{
    Q_OBJECT
public:
    explicit MyThread(QObject *parent = 0);
protected:
    void run();
public slots:
    void slotFunc();
private:
    QTimer *timer;    
};
#endif // MYTHREAD_H

//mythread.cpp
#include "mythread.h"
#include <QtDebug>
#include <QTimer>
MyThread::MyThread(QObject *parent) :
    QThread(parent)
{
}
void MyThread::run()
{
    timer = new QTimer;
    timer->setSingleShot(true);
    timer->start(5000);
	qDebug() << timer << QThread::currentThreadId();
    connect(timer, SIGNAL(timeout()), this, SLOT(slotFunc()));
    exec();
    qDebug() << "********************";
}
void MyThread::slotFunc()
{
    qDebug() << timer << QThread::currentThreadId();
    qDebug() << "#################";
}

//main.cpp
#include <QApplication>
#include "mythread.h"
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MyThread obj;
    obj.start();
    return a.exec();
}

程序运行输出结果:

QTimer(0xb51019e8)3047508848

QTimer(0xb51019e8)3078874896

#################

由输出结果可知发射信号和槽函数执行正常,这时可能就有人这样思考,exec()是阻塞线程的,同样死循环也是阻塞的,而且更容易被我们控制(我就是这样想的),下面我们尝试一下,将run()函数中的exec()替换为

    int time = 5;
    while(time > 0) {
        sleep(2);
        time--;
    }

然后运行程序,输出:

QTimer(0xb50019e8)3046460272

********************

由此可见,要真正实现子线程中的信号与槽的连接,必须得执行exec()函数进入子线程的事件循环。其实这个函数我们并不陌生,Qt的main函数中基本上都有如下的程序:

    QApplication a(argc, argv);
    ...    //do something
    return a.exec();

其实这段程序就是要进入主线程的事件循环,这样事件才会被传递处理,若写成return 0;,程序会异常退出。

 

细心的读者可能已经发现,在第一个程序的输出结果中,timer的地址相同,但它却处于两个不同的线程中,也就是说,timer在一个线程中创建,然后在另一个线程中执行,如果在这个线程中执行删除操作则可能会出现意想不到的结果,除非mutex用的好。这时如果将slotFunc()函数修改为

void MyThread::slotFunc()
{
    qDebug() << timer << QThread::currentThreadId();
    qDebug() << "#################";
    timer->start(5000);
}

然后执行程序,

QTimer(0xb51019e8) 3047361392

QTimer(0xb51019e8)3078637328

#################

程序仅仅输出一次,而不是像预期的那样,每隔5s输出一次,这就是因为timer是处在两个不同的线程中,而如果我们改变一下信号与槽的连接方式:

connect(timer, SIGNAL(timeout()), this, SLOT(slotFunc()), Qt::DirectConnection);

运行程序,有

QTimer(0xb50019e8)3046460272

QTimer(0xb50019e8)3046460272

#################

QTimer(0xb50019e8)3046460272

#################

QTimer(0xb50019e8)3046460272

#################

这样程序就会每隔5s输出一次,这时timer已经处于同一个线程了(可以通过改变信号与槽的连接方式改变槽函数的执行方式,不明白请了解Qt信号与槽的连接方式)。


上面讲解了exec()函数,对于多线程程序中函数执行在哪个线程的问题,请参考博客http://blog.csdn.net/sydnash/article/details/7425947,要说明的是,如果在博客的mainwindow.cpp的构造函数中connect(firstButton,SIGNAL(clicked()),this,SLOT(onFirstPushed()));这样创建连接,同样是调用了MyObject::first()函数,但此时是槽函数时在主线程中执行的,另外,文章也说明了在使用线程的时候,可以将一个类派生自QObject,然后实现所有的signal/slot,然后通过调用movetothread函数来使他们执行在新的线程里面,而不是每次都要重新派生QThread,并且派生QThread函数的另外一个不好的地方是只有run函数内部的代码才会执行在新线程里 面,相比起来,派生QObject并使用movetothread函数更具有灵活性,如同程序中执行QThread *thread = new Qthread; my->moveToThread(thread);。

 

另外,在Qt多线程编程中,如果用到QTcpSocket类,可能会出现如下警告:

QObject: Cannot create children for aparent that is in a different thread.

(Parent is QTcpSocket(0x8373b28), parent'sthread is QThread(0x81c9298),current thread is ConnThread(0x8373b18)

这是因为创建和使用QTcpSocket对象不是在同一个线程,只要参照上面给出的博客的方法,使得在同一个线程创建和使用QTcpSocket对象就能避免警告。

 

最后,若有如下程序(.h文件未给出):

#include "newobject.h"
#include <QTcpSocket>
NewObject::NewObject(QObject *parent) :
    QObject(parent)
{
    readBuffer = new char[1500];
}
void NewObject::createVariables()
{
    socket = new QTcpSocket;
    connect(socket, SIGNAL(readyRead()), this, SLOT(readData()));
}
void NewObject::readData()
{
    while(socket->bytesAvailable()) {
        int size = socket->read(readBuffer, 1500);
        printf("size = %d\n", size);
    }
}

QThread *newThread = new QThread;
    newObj = new NewObject;
    newObj->moveToThread(newThread);
    newThread->start();

当执行下面代码时,虽然不会报告任何警告或出错信息,但是在执行newObj=newNewObject;时会执行readBuffer=newchar[1500];语句,这时程序执行在主线程中,也就是说,readBuffer是在主线程中创建的内存区域,而使用是在子线程中,这样程序在执行过程中会出现意想不到的错误而导致崩溃(我本人为此崩溃了许久,通过后面给出的方法修改终于稳定),因此要保证通过new创建对象和使用在同一个线程,可以将readBuffer=newchar[1500];置于createVariables()函数中,然后添加:

connect(newThread,SIGNAL(started()), newObj, SLOT(createVariables()));

同样,通过下面语句来销毁对象:

connect(newThread,SIGNAL(finished()), newObj, SLOT(releaseVariables()));

其中 releaseVariables()函数执行delete操作。


好了,就是这些,多线程编程本身就比较复杂,而Qt封装了许多实现后若对其用法不是很明确会遇到各种各样的异常,出现异常,很多时候是由于我们使用方法不对,而不是对多线程理解有误,无论如何,我们只有在使用中摸索。

写了这么多,希望能够帮到大家,不对之处欢迎大家指正~~~~~~



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章