工作需要,前段时间要写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信号与槽的连接方式)。
另外,在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封装了许多实现后若对其用法不是很明确会遇到各种各样的异常,出现异常,很多时候是由于我们使用方法不对,而不是对多线程理解有误,无论如何,我们只有在使用中摸索。
写了这么多,希望能够帮到大家,不对之处欢迎大家指正~~~~~~