12 Qt4及Qt5的多线程编程

12 Qt4及Qt5的多线程编程_第1张图片

一个可重入的类,指的是它的成员函数可以被多个线程安全地调用,只要每个线程使用这个类的不同的对象。而一个线程安全的类,指的是它的成员函数能够被多线程安全地调用,即使所有的线程都使用该类的同一个实例也没有关系。

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多线程编程注意事项:

  1. 线程不能操作UI对象(从Qwidget直接或间接派生的窗口对象)
  2. 需要移动到子线程中处理的模块类,创建的对象的时候不能指定父对象。

关于Qobject类的connect函数最后一个参数,连接类型:

  1. 自动连接(AutoConnection),默认的连接方式。:如果信号与槽,也就是发送者与接受者在同一线程,等同于直接连接,如果发送者与接受者处在不同线程,等同于队列连
  2. 直接连接(DirectConnection):当信号发射时,槽函数立即直接调用。无论槽函数所属对象在哪个线程,槽函数总在发送者所在线程执行。
  3. 队列连接(QueuedConnection):当控制权回到接受者所在线程的事件循环时,槽函数被调用。槽函数在接受者所在线程执行。
  4. Qt::BlockingQueuedConnection:槽函数的调用时机与Qt::QueuedConnection一致,不过发送完信号后发送者所在线程会阻塞,直到槽函数运行完。接收者和发送者绝对不能在一个线程,否则程序会死锁。在多线程间需要同步的场合可能需要这个。
  5. Qt::UniqueConnection:这个flag可以通过按位或(|)与以上四个结合在一起使用。当这个flag设置时,当某个信号和槽已经连接时,再进行重复的连接就会失败。也就是避免了重复连接。
     

1、Qt4.6之前的多线程

  1. 新建一个类MyThread,基类为QThread。
  2. 重写类MyThread的虚函数void run();,即新建一个函数protected void run(),然后对其进行定义。
  3. 在需要用到多线程的地方,实例MyThread,然后调用函数MyThread::start()后,则开启一条线程,自动运行函数run()。注意不能直接调用run()函数,只能通过start()间接调用。
  4. 当停止线程时,调用MyThread::wait()函数,等待线程结束,并且回收线程资源。

示例代码如下:

mythread.h

#ifndef MYTHREAD_H
#define MYTHREAD_H

#include 

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 
#include 
#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 
MyThread::MyThread(QObject *parent) : QThread(parent)
{

}
//重写run函数,即线程执行函数
void MyThread::run()
{
    int i = 0;
    while(i < 10)
    {
        i++;
        sleep(1);
        qDebug()<<"我是子线程,我的ID是:"<

mywidget.cpp

#include "mywidget.h"
#include "ui_mywidget.h"
#include 
#include 
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是:"<start();
}

最后运行结果如下:

我是主线程,我的ID是: 0x3f14

我是主线程,我的ID是: 0x3f14

我是主线程,我的ID是: 0x3f14

我是主线程,我的ID是: 0x3f14

我是子线程,我的ID是: 0x36d4

子线程is Done

 

2、Qt4.6之后的多线程

  1. 新建一个类,继承与QObject
  2. 在类中创建一个槽函数,用于运行多线程里面的代码。所有耗时代码,全部在这个槽函数里面运行。
  3. 实例一个该类对象myThread,注意不能指定父对象,因为后面后面通过moveToThread再指定父对象
  4. 实例一个QThread对象thread,此时可以指定父对象
  5. 将QThread对象转到新建类的实例对象myThread中,可以使用函数void QObject::moveToThread(QMyThread *thread);如:myThread->moveToThread(thread)
  6. 启动线程对象thread->start();
  7. 通过信号与槽的方式,启动新建类myThread的槽函数
  8. 使用线程对象thread的quit()及wait()函数等待子线程退出,注意wait()会等待槽函数执行结束后才返回,如果槽函数一直不结束,程序可能卡死

示例代码如下:

mthread.h

#ifndef MYTHREAD_H
#define MYTHREAD_H

#include 
#include 
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 
#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
MyThread::MyThread(QObject *parent) : QObject(parent)
{
    isStop = false;
}

MyThread::~MyThread()
{

}

void MyThread::myTimeOut()
{
    while(!isStop)
    {
        qDebug()<<"我是子线程,我的ID是:"<

mywidget.cpp

#include "mywidget.h"
#include "ui_mywidget.h"
#include 
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是:"<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

 

你可能感兴趣的:(QT教程)