一个可重入的类,指的是它的成员函数可以被多个线程安全地调用,只要每个线程使用这个类的不同的对象。而一个线程安全的类,指的是它的成员函数能够被多线程安全地调用,即使所有的线程都使用该类的同一个实例也没有关系。
QObject是可重入的。它的大多数非GUI子类,例如:QTimer、QTcpSocket、QUdpSocket和QProcess,也都是可重入的,这使得在多线程中同时使用这些类成为可能。注意:这些类被设计成在单一线程中创建和使用的,在一个线程中创建一个对象而在另一个线程中调用该对象的函数,不保证能行得通。需要注意有三个约束:
- 一个QObject类型的孩子必须总是被创建在它的父亲所被创建的线程中。这意味着,除了别的以外,永远不要把QThread对象(this)作为该线程中创建的一个对象的父亲(因为QThread对象自身被创建在另外一个线程中)。
- 事件驱动的对象可能只能被用在一个单线程中。特别是,这适用于计时器机制(timer mechanism)和网络模块。例如:你不能在不属于这个对象的线程中启动一个定时器或连接一个socket,必须保证在删除QThread之前删除所有创建在这个线程中的对象。在run()函数的实现中,通过在栈中创建这些对象,可以轻松地做到这一点。
- 虽然QObject是可重入的,但GUI类,尤其是QWidget及其所有子类都不是可重入的,它们只能被用在主线程中。如前面所述,QCoreApplication::exec()必须也从那个线程被调用。
Qt开启多线程,主要用到类QThread。有两种方法,第一种用一个类继承QThread,然后重新改写虚函数run()。当要开启新线程时,只需要实例该类,然后调用函数start(),就可以开启一条多线程。第二种方法是继承一个QObject类,然后利用moveToThread()函数开启一个线程槽函数,将要花费大量时间计算的代码放入该线程槽函数中。
Qt多线程编程注意事项:
- 线程不能操作UI对象(从Qwidget直接或间接派生的窗口对象)
- 需要移动到子线程中处理的模块类,创建的对象的时候不能指定父对象。
关于Qobject类的connect函数最后一个参数,连接类型:
- 自动连接(AutoConnection),默认的连接方式。:如果信号与槽,也就是发送者与接受者在同一线程,等同于直接连接,如果发送者与接受者处在不同线程,等同于队列连
- 直接连接(DirectConnection):当信号发射时,槽函数立即直接调用。无论槽函数所属对象在哪个线程,槽函数总在发送者所在线程执行。
- 队列连接(QueuedConnection):当控制权回到接受者所在线程的事件循环时,槽函数被调用。槽函数在接受者所在线程执行。
- Qt::BlockingQueuedConnection:槽函数的调用时机与Qt::QueuedConnection一致,不过发送完信号后发送者所在线程会阻塞,直到槽函数运行完。接收者和发送者绝对不能在一个线程,否则程序会死锁。在多线程间需要同步的场合可能需要这个。
- Qt::UniqueConnection:这个flag可以通过按位或(|)与以上四个结合在一起使用。当这个flag设置时,当某个信号和槽已经连接时,再进行重复的连接就会失败。也就是避免了重复连接。
1、Qt4.6之前的多线程
- 新建一个类MyThread,基类为QThread。
- 重写类MyThread的虚函数void run();,即新建一个函数protected void run(),然后对其进行定义。
- 在需要用到多线程的地方,实例MyThread,然后调用函数MyThread::start()后,则开启一条线程,自动运行函数run()。注意不能直接调用run()函数,只能通过start()间接调用。
- 当停止线程时,调用MyThread::wait()函数,等待线程结束,并且回收线程资源。
示例代码如下:
mythread.h
#ifndef MYTHREAD_H
#define MYTHREAD_H
#include <QThread>
class MyThread : public QThread
{
Q_OBJECT
public:
explicit MyThread(QObject *parent = 0);
~MyThread();
protected:
//重写run函数,即线程执行函数
void run();
signals:
//自定义的子线程执行完毕后发出的信号
void isDone();
public slots:
};
#endif // MYTHREAD_H
mywidget.h
#ifndef MYWIDGET_H
#define MYWIDGET_H
#include <QWidget>
#include <QTimer>
#include "mythread.h"
namespace Ui {
class MyWidget;
}
class MyWidget : public QWidget
{
Q_OBJECT
public:
explicit MyWidget(QWidget *parent = 0);
~MyWidget();
private slots:
//定时器超时处理槽函数
void dealTimeOut();
void on_pushButton_clicked();
//子线程退出时的处理函数
void dealDone();
//点击右上角红叉进行子线程退出的槽函数
void stopThread();
private:
Ui::MyWidget *ui;
QTimer MyTimer;
MyThread* thread;
};
#endif // MYWIDGET_H
mythread.cpp
#include "mythread.h"
#include <QDebug>
MyThread::MyThread(QObject *parent) : QThread(parent)
{
}
//重写run函数,即线程执行函数
void MyThread::run()
{
int i = 0;
while(i < 10)
{
i++;
sleep(1);
qDebug()<<"我是子线程,我的ID是:"<<QThread::currentThreadId();
}
emit isDone();
}
MyThread::~MyThread()
{
}
mywidget.cpp
#include "mywidget.h"
#include "ui_mywidget.h"
#include <QThread>
#include <QDebug>
MyWidget::MyWidget(QWidget *parent) :
QWidget(parent),
ui(new Ui::MyWidget)
{
ui->setupUi(this);
thread = new MyThread(this);
//关联信号与槽
connect(&this->MyTimer,QTimer::timeout,this,&MyWidget::dealTimeOut);
connect(thread,&MyThread::isDone,this,&MyWidget::dealDone);
connect(this,&MyWidget::destroy,this,&MyWidget::stopThread);
}
void MyWidget::stopThread()
{
//退出
thread->quit();
//等待子进程函数执行完毕后,再退出
thread->wait();
}
void MyWidget::dealDone()
{
MyTimer.stop();
qDebug()<<"子线程is Done";
}
void MyWidget::dealTimeOut()
{
static int i = 0;
i++;
ui->lcdNumber->display(i);
qDebug()<<"我是主线程,我的ID是:"<<QThread::currentThreadId();
}
MyWidget::~MyWidget()
{
delete ui;
}
void MyWidget::on_pushButton_clicked()
{
if(MyTimer.isActive() == false)
{
//启动定时器
MyTimer.start(100);
}
//启动定时器
thread->start();
}
最后运行结果如下:
我是主线程,我的ID是: 0x3f14
我是主线程,我的ID是: 0x3f14
我是主线程,我的ID是: 0x3f14
我是主线程,我的ID是: 0x3f14
我是子线程,我的ID是: 0x36d4
子线程is Done
2、Qt4.6之后的多线程
- 新建一个类,继承与QObject
- 在类中创建一个槽函数,用于运行多线程里面的代码。所有耗时代码,全部在这个槽函数里面运行。
- 实例一个该类对象myThread,注意不能指定父对象,因为后面后面通过moveToThread再指定父对象
- 实例一个QThread对象thread,此时可以指定父对象
- 将QThread对象转到新建类的实例对象myThread中,可以使用函数void QObject::moveToThread(QMyThread *thread);如:myThread->moveToThread(thread)
- 启动线程对象thread->start();
- 通过信号与槽的方式,启动新建类myThread的槽函数
- 使用线程对象thread的quit()及wait()函数等待子线程退出,注意wait()会等待槽函数执行结束后才返回,如果槽函数一直不结束,程序可能卡死
示例代码如下:
mthread.h
#ifndef MYTHREAD_H
#define MYTHREAD_H
#include <QObject>
#include <QThread>
class MyThread : public QObject
{
Q_OBJECT
public:
explicit MyThread(QObject *parent = 0);
~MyThread();
//自定义线程处理函数,不能直接调用,否则函数和主线程在一个线程内,
//如果该函数有耗时操作,线程将会卡死
void myTimeOut();
signals:
//自定义信号,用来通知主线程进行相关操作
void mysignals();
public:
bool isStop;
public slots:
};
#endif // MYTHREAD_H
mywidget.h
#ifndef MYWIDGET_H
#define MYWIDGET_H
#include <QWidget>
#include "mythread.h"
namespace Ui {
class MyWidget;
}
class MyWidget : public QWidget
{
Q_OBJECT
public:
explicit MyWidget(QWidget *parent = 0);
~MyWidget();
signals:
void myStartThread();
private slots:
//启动按钮的回调函数
void on_startBtn_clicked();
//停止按钮的回调函数
void on_pushButton_2_clicked();
//处理子进程的槽函数
void dealSignals();
//退出程序时的槽函数
void stopThread();
private:
//自定义线程类对象
MyThread* mythread;
//线程类对象
QThread* thread;
Ui::MyWidget *ui;
};
#endif // MYWIDGET_H
mypthread.cpp
#include "mythread.h"
#include<QDebug>
MyThread::MyThread(QObject *parent) : QObject(parent)
{
isStop = false;
}
MyThread::~MyThread()
{
}
void MyThread::myTimeOut()
{
while(!isStop)
{
qDebug()<<"我是子线程,我的ID是:"<<QThread::currentThreadId();
QThread::sleep(1);
//给主线程发送信号
emit mysignals();
}
}
mywidget.cpp
#include "mywidget.h"
#include "ui_mywidget.h"
#include <QDebug>
MyWidget::MyWidget(QWidget *parent) :
QWidget(parent),
ui(new Ui::MyWidget)
{
ui->setupUi(this);
//动态分配空间,不可以指定父对象
mythread = new MyThread;
//创建子线程
thread = new QThread(this);
//将自定义线程加入到子线程中去,即指定子线程位自定义线程的父亲
mythread->moveToThread(thread);
//建立主线程与子线程的信号与槽
connect(mythread,&MyThread::mysignals,this,&MyWidget::dealSignals);
//通过信号与槽方式,调用子线程的线程处理函数
connect(this,&MyWidget::myStartThread,mythread,&MyThread::myTimeOut);
//关闭操作时,进行线程退出操作
connect(this,&MyWidget::destroyed,this,&MyWidget::stopThread);
qDebug()<<"我是主线程,我的ID是:"<<QThread::currentThreadId();
}
//线程退出操作
void MyWidget::stopThread()
{
if(thread->isRunning())
{
mythread->isStop = true;
thread->quit();
thread->wait();
}
}
//主线程根据子线程信号进行槽函数操作
void MyWidget::dealSignals()
{
static int i = 0;
i++;
ui->lcdNumber->display(i);
}
MyWidget::~MyWidget()
{
delete ui;
}
//启动按钮的槽函数
void MyWidget::on_startBtn_clicked()
{
if(false == thread->isRunning())
{
//启动子线程,但是不启动自定义线程的处理函数
thread->start();
mythread->isStop = false;
emit myStartThread();
}
}
//停止按钮的槽函数
void MyWidget::on_pushButton_2_clicked()
{
if(thread->isRunning())
{
mythread->isStop = true;
thread->quit();
thread->wait();
}
}
代码运行结果如下:
我是主线程,我的ID是: 0x19e0
我是子线程,我的ID是: 0x4430
我是子线程,我的ID是: 0x4430