Qt——进程和线程


进程QProcess


使用QProcess.start(程序名称,命令参数)启动进程的步骤:

状态
状态
状态
start函数
QProcess::Starting
QProcess::Running
QProcess::Running
started信号
finished信号
此外每次状态改变都会有QProcess::stateChanged信号

线程QThread


概要

要创建一个线程,必须子类化QThread,并重新实现run()

开始信号
结束信号
终止信号
Thread
started
finished
terminated
函数 功能
isFinished() 查看线程状态
isRunning() 查看线程状态
exec() 启动事件循环
exit() 或 quit() 停止事件循环
terminate() 强行终止一个线程,不推荐
wait() 跟随terminate()之后来同步终止
currentThreadId() 返回当前线程的ID
currentThread() 返回当前线程的指针

-----重点:讲述具体如何使用多线程-----

首先,非常感谢两篇博文:

  • Qt使用多线程的一些心得——1.继承QThread的多线程使用方法
  • Qt使用多线程的一些心得——2.继承QObject的多线程使用方法

作为初学的话,建议仔细看,多看几遍这两篇文章,写得非常好~虽然刚开始看可能由于作者有很多具体的代码,以及很多情况,所以篇幅略长看不进去,但是多看几遍加自己一些思考,就可以初步学会怎么使用两种多线程建立的方法。我写这篇博文主要是再具体总结强调一些我实践过程中感觉到的重点部分,可能会和前面两篇博文有一些重合地方。

线程调用方法1——直接用start

此方法最重要的是记住三点:

  1. 只有run函数是在新建的线程中,而其他类函数都在原线程
  2. 线程如何开始: myThreadClass.start()
  3. 线程何时结束: 如果没有加入exec()让事件循环,那么run函数运行完即结束

为什么要知道这三点,因为它们关乎线程有没有被成功建立和成功释放,否则就会出很大的事,比如线程没被释放的时候会不断创建线程导致代码被反复执行多次。

我抓住这几个重点写最简略的代码,防止其他内容干扰了理解。一些说明也写在了代码注释中。
MyThread.h:创建线程类

#include 
class MyThread:public QThread{
	Q_OBJECT
public:
	explicit MyThread(QObject *parent=0);
protected:
	//run函数的重载是线程耗时事件执行的关键
	void run();
};

MyThread.cpp

#include "MyThread.h"
#include 

MyThread::MyThread(QObject *parent) :
    QThread(parent)
{
}

void MyThread::run()
{
   	//在此写上耗时操作
}

dialog.h:主窗口的类

#ifndef DIALOG_H
#define DIALOG_H

#include 
#include "mythread.h"

namespace Ui {
class Dialog;
}

class Dialog : public QDialog
{
    Q_OBJECT

public:
    explicit Dialog(QWidget *parent = 0);
    ~Dialog();

private slots:
    void clicked_thread_in(); //对应UI界面点击按钮进入线程

private:
    Ui::Dialog *ui;
    MyThread thread;  //建立需要的线程,如果是全局线程就这么做;最后析构函数也会被释放
};

#endif // DIALOG_H

dialog.cpp:要使用线程在对应地方start即可

include "dialog.h"
#include "ui_dialog.h"

Dialog::Dialog(QWidget *parent) :
    QDialog(parent),
    ui(new Ui::Dialog)
{
    ui->setupUi(this);
    //连接按钮和槽
    connect(ui->pushButton,&QPushButton::clicked,this,&Widget::clicked_thread_in);
}

Dialog::~Dialog()
{
    delete ui;
}

// 启动线程按钮
void Dialog::clicked_thread_in()
{
	//使用线程
    thread.start();
}

上面是全局线程怎么做,但是更常用的是局部线程,只在用的时候开出来,那么需要做点改动:
dialog.h:主窗口的类中用指针声明线程

private:
MyThread *thread; 

dialog.cpp:定义线程并且配合QObject::deleteLater

// 启动线程按钮
void Dialog::clicked_thread_in()
{	
	//定义线程
	thread = new MyThread();
	//*********让线程结束后自动销毁的关键
	connect(thread,&QThread::finished,thread,&QObject::deleteLater)
	//使用线程
    thread->start();
}

线程调用方法2——QObject::moveToThread

qt官方文档,这是必须先通读一遍的代码!

class Worker : public QObject
  {
      Q_OBJECT

  public slots:
      void doWork(const QString &parameter) {
          QString result;
          /* ... here is the expensive or blocking operation ... */
          emit resultReady(result);
      }

  signals:
      void resultReady(const QString &result);
  };

  class Controller : public QObject
  {
      Q_OBJECT
      QThread workerThread;
  public:
      Controller() {
          Worker *worker = new Worker;
          worker->moveToThread(&workerThread);
          connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater);
          connect(this, &Controller::operate, worker, &Worker::doWork);
          connect(worker, &Worker::resultReady, this, &Controller::handleResults);
          workerThread.start();
      }
      ~Controller() {
          workerThread.quit();
          workerThread.wait();
      }
  public slots:
      void handleResults(const QString &);
  signals:
      void operate(const QString &);
  };

同样先提下类似与上面关键的三个地方:

  1. 类中所有函数都在新建的线程中
  2. 线程如何开始: workThread.start(),道理是和前面的方法一样,都是利用线程类QThread启动,但是因为此时我们定义的类继承自QObject所以不是用定义的类对象来启动了
  3. 线程何时结束: 整个线程默认是不断循环的,所以这时候quit()函数就起到关键作用了,因为官方说明其作用并不是让线程退出和结束,而是退出线程循环,如果不退循环线程是一直在的。

官方介绍的是如何建立全局线程了,那我就在下面讲怎么创建使用更多的局部线程这点是前面两篇文章可能漏写的,以及官方文档没写的
为了方便理解,下述代码执行流程是:

定义临时线程并开启
发射信号work1_ready
单击按钮
窗口槽 clicked_thread_in 响应
线程类 work1 函数响应
窗口槽 work1_ready_call 响应
主线程中结束子进程

MyThreadObj.h:创建线程类

#include 
class MyThreadObj : public QObject{
	Q_OBJECT
public:
	explicit MyThreadObj(QObject *parent=0);
protected:
	//可以定义多个工作函数,为了代码简单就只写一个
	void work1();
signals:
	//表示可以暂停线程了
	void work1_ready();
};

MyThreadObj.cpp

#include "MyThreadObj.h"
#include 

MyThreadObj::MyThreadObj(QObject *parent) :
    QObject (parent)
{
}

void MyThreadObj::work1()
{
   	//在此写上耗时操作
   	...
   	//此外一定要发射一个信号表示可以暂停线程了
   	emit work1_ready();
}

dialog.h:主窗口的类

#ifndef DIALOG_H
#define DIALOG_H

#include 
#include "MyThreadObj.h"

namespace Ui {
class Dialog;
}

class Dialog : public QDialog
{
    Q_OBJECT

public:
    explicit Dialog(QWidget *parent = 0);
    ~Dialog();

private slots:
    void clicked_thread_in(); //对应UI界面点击按钮进入线程
    void work1_ready_call();  //来自线程类发射的work1_ready信号

private:
    Ui::Dialog *ui;
    MyThread *thread;  //关键:一定要先声明需要的线程,不可以局部声明,否则没法释放
};

#endif // DIALOG_H

dialog.cpp:要使用线程在对应地方start即可

include "dialog.h"
#include "ui_dialog.h"

Dialog::Dialog(QWidget *parent) :
    QDialog(parent),
    ui(new Ui::Dialog)
{
    ui->setupUi(this);
    //连接按钮和槽
    connect(ui->pushButton,&QPushButton::clicked,this,&Widget::clicked_thread_in);
}

Dialog::~Dialog()
{
    delete ui;
}

// 启动线程按钮
void Dialog::clicked_thread_in()
{
	//建立使用线程类
	MyThreadObj *obj1 = new MyThreadObj();
	//定义新的线程,关键:线程绝对不可以有父对象
	thread = new QThread(NULL);
	//线程结束的时候释放类对象,一定要有
	connect(thread,&QThread::finished,obj1,&QObject::deleteLater)
	//连接线程完成发射信号
	connect(obj1,&MyThreadObj::work1_ready,this,&Dialog::work1_ready_call);
	//开启线程
    thread->start();
}

//线程类的信号应答,并关闭线程
void Dialog::work1_ready_call(){
	thread->quit(); //让线程退出循环,此处即可退出线程
	thread->wait();	//等待线程释放
}

那么问题来了,前面说的那种出事是怎么样?
先看看正常情况

out: “MVIRDD::MVIRDD -> 主线程 thread id:19424”

out: 子线程dataTrans线程start
out: TimeuseWorker::dataTrans -> 子线程dataTrans, thread id:16676
out: 数据传输线程启动
out: 数据传输完成

再看看不正常情况
按理说每次按按钮都是上面这样,但是如果线程没被释放,就会重复执行,多按几次程序就奔溃了

out: “MVIRDD::MVIRDD -> 主线程 thread id:18440”

按一次按钮
out: 子线程dataTrans线程start
out: TimeuseWorker::dataTrans -> 子线程dataTrans, thread id:10124
out: 数据传输线程启动
out: 数据传输完成
按两次按钮
out: 子线程dataTrans线程start
out: TimeuseWorker::dataTrans -> 子线程dataTrans, thread id:10124
out: 数据传输线程启动
out: 数据传输线程启动
out: 数据传输完成
out: 数据传输完成

------重点结束-------

同步线程

方法 含义
QMutex 互斥锁: 只允许一个线程访问变量
QReadWriteLock 读写锁: 允许多个线程“读”,但只能一个线程“写”
QSemaphore 信号量: QMutex只能保护一个量,而它可以保护多个数量的相同资源
QWaitCondition 条件变量: 允许一个线程在一些条件满足时唤醒其他线程
#include 
#include 
#include 
#include 

const int DataSize = 10;   	//写入和读取的次数
const int BufferSize = 5;	//缓存大小
char buffer[BufferSize];    //缓存空间,超出范围从头开始写
QSemaphore freeBytes(BufferSize);  //producer未写或consumer已读
QSemaphore usedBytes;			   //producer已写或consumer未读

// 生产者
class Producer : public QThread
{
public:
    void run();
};

void Producer::run()
{
    qsrand(QTime(0,0,0).secsTo(QTime::currentTime()));
    for (int i = 0; i < DataSize; ++i) {
        freeBytes.acquire();
        buffer[i % BufferSize] = "ACGT"[(int)qrand() % 4];
        qDebug() << QString("producer: %1").arg(buffer[i % BufferSize]);
        usedBytes.release();
    }
}

// 消费者
class Consumer : public QThread
{
public:
    void run();
};

void Consumer::run()
{
    for (int i = 0; i < DataSize; ++i) {
        usedBytes.acquire();
        qDebug() << QString("consumer: %1").arg(buffer[i % BufferSize]);
        freeBytes.release();
    }
}


int main(int argc, char *argv[])
{
    QCoreApplication app(argc, argv);
    Producer producer;
    Consumer consumer;
    producer.start();
    consumer.start();
    producer.wait();
    consumer.wait();
    return app.exec();
}

某次运行的结果

次数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
producer G T C C A G G T G T
consumer G T C C A G G T G T

你可能感兴趣的:(Qt学习入门)