一文教会你Qt5多线程及线程池的使用方法

一. QThread创建普通线程的方法

我的单个创建的线程一般都是一类需求创建一个线程,比如我有很多打开文件读取文件内容的需求,所以我可以创建一个线程来专门打开并读取文件,我只需要告诉这个线程我要打开的文件地址就可以了。
所以我在主线程中要做的只有运行这个自线程,然后当我有需要打开的文件的时候,将这个文件地址告诉新线程。
子线程也只需要接收这个地址,并在有地址传过来的时候打开并读取就好了。
1. 先在项目中创建新的类文件,步骤如下图所示:
选择c++ class 或者 c/c++ header, 我这选择Class
因为在线程中与主线程进行数据交互需要用到信号与槽函数,所以我这里Base类选择QObject, 并且添加QObject头文件和宏,这样就可以使用信号槽了然后下一步直到创建好文件。如下图所示:
在这里插入图片描述
修改这个类继承的基类为:QThread, 修改后如下图所示:
在这里插入图片描述这样线程类就创建好了。

2.线程类创建完成后开始进行run() 函数的重写及任务队列的创建。
先在类里声明一个队列QQueue, 用来传输任务.。并且再创建一个锁来防止我主线程给这个Queue中放数据时子线程在取数据,造成程序崩溃:

#include <QQueue>
#include <QMutex>
class ThreadTest : public QThread{
	...//类里原有的其他内容,这里不细写
	QQueue<QString> queue;//我这里假设的场景:传递某些文件地址,在多线程里打开并读取再将数据发送给主线程进行显示。所以是个字符串类型。
	QMutex mutex;//创建一个锁
}

在类里声明重写run()函数:

class ThreadTest : public QThread{
	...
	void run() override{
		while(1){	//我需要这个线程一直保持运行
			if(!queue.isEmpty){	//当我的任务队列中有需要打开的文件地址时就开始读取,没有时就空闲
				mutex.lock();	//在操作队列前必须加锁,防止多个线程同时操作造成崩溃。
				qDebug() << queue.dequeue();	//取出并打印地址
				mutex.unlock();	//解锁
				QThread::msleep(3000);	//3秒延时,读取文件的操作不细写了,用延时来代替
			}
		}
	}
}

现在在这个类里面写一个Queue接收数据的函数以及主线程中放数据和开始子线程的函数就可以了。

class ThreadTest : public QThread{
	...
	void setQueue(QString str){
		mutex.lock();//加锁
		queue.enqueue(str);//放入数据
		mutex.unlock();//解锁
	}
}

OK,现在线程类的内容算是写完了,整体代码如下所示:

#include <QThread>
#include <QMutex>
#include <QDebug>
#include <QQueue>

class ThreadTest : QThread{
	Q_OBJECT;	//QObject宏
private:
	QQueue<QString> queue;
	QMutex mutex;
public:
	explicit ThreadTest(QObject *parent = nullptr);
	void run() override{
		while(1){
			if(!queue.isEmpty()){
				mutex.lock();
				qDebug() << queue.dequeue();
				mutex.unlock();
				QThread::msleep(3000);
			}
		}
	}
	void setQueue(QString str){
		mutex.lock();//加锁
		queue.enqueue(str);//放入数据
		mutex.unlock();//解锁
	}
};

好了,接下来在主线程中new一个线程类的实例开始运行,并往队列里实时写数据就可以了:

ThreadTest *test = new ThreadTest;
test->start();
for(int i = 0; i < pathVector.Size(); ++i){
	test->setQueue(pathVector[i]);
}

二. 使用QRunnable创建任务并放置在QThreadPool线程池中运行

基于QRunnable创建子类,在这个子类中重写run()方法,重写完成后在主线程中创建一个QThreadPool对象tp,先调用setMaxThreadCount()函数给tp设置一个最大线程数量,再调用start()函数,将创建的QRunnable的子类对象指针作为参数放置到start(子类对象指针)中进行执行任务。
当QThreadPool被调用足够多的start()函数时,且线程池的线程还在执行先放进去的任务时,不用担心你后面放进去的任务会被丢弃掉,QThreadPool自带有任务队列,会顺序执行你调用的所有start()放进去的任务。
1. 先进行QRunnable子类的创建,因为整个子类中,只有run()函数是有用的,所以我这里选择创建一个.h文件(其实上面也一样都是在.h里写的代码,但是没有什么影响)
在这里插入图片描述创建完成:
在这里插入图片描述创建一个继承QRunnable的子类,如下图所示:

#include <QRunnable>
#include <QThread>
#include <QDebug>

class ThreadPoolTest : public QRunnable{
private:
	QString m_str;
public:
	ThreadPoolTest(QString str){//构造函数,当对象创建时,将需要的参数传输进来并赋值给类成员变量,以供其他成员函数使用(比如当前例子的run()函数)。
		m_str = str;
	}
	void run() override{
		//写入要执行的操作,比如读取文件,这里用延时3s来代替
		QThread::msleep(3000);
		qDebug() << m_str ;
	}	
};

2. 子类创建完成后,在主线程中创建QThreadPool对象,并设置线程数,调用start()

QThreadPool tp;
tp.setMaxThreadCount(10);
//pathVector 需要打开的文件路径
for(int i = 0; i < pathVector.Size(); ++i){
	//当我有i个文件需要线程池帮我读取时,我只需要将这个地址创建出一个任务,将这个任务通过tp.start()传给线程池处理就行了。
	//QThreadPool自带任务队列,所以不用担心传的太快线程池来不及处理。
	tp.start(new ThreadPoolTest(pathVector[i]));
}

三. 使用QtConcurrent来进行线程池并发操作,最简单,推荐。

使用这个方法不用再繁琐的创建这个类那个类的派生类了,将普通的类成员函数方法到QtConcurrent::run()函数中运行就可以了。

先在pro文件中添加concurrent支持

QT += concurrent

使用方法:

//主线程中
//比如我需要将当前主线程类的readFile(QString str)函数放到线程池中运行,如下所示:
for(int i  = 0; i < pathVector.Size(); ++i){
	//不需要设置线程池的大小,线程池线程数会自适应创建多少个线程。同样自带任务队列,往里面放任务即可
	QtConcurrent::run(this, &MainWindow::readFile, pathVector[i]);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值