一. QThread创建普通线程的方法
我的单个创建的线程一般都是一类需求创建一个线程,比如我有很多打开文件读取文件内容的需求,所以我可以创建一个线程来专门打开并读取文件,我只需要告诉这个线程我要打开的文件地址就可以了。
所以我在主线程中要做的只有运行这个自线程,然后当我有需要打开的文件的时候,将这个文件地址告诉新线程。
子线程也只需要接收这个地址,并在有地址传过来的时候打开并读取就好了。
1. 先在项目中创建新的类文件,步骤如下图所示:
然后下一步直到创建好文件。如下图所示:
修改这个类继承的基类为: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]);
}