Qt 多线程基础及线程使用方式

544 阅读10分钟

文章目录

Qt 多线程操作

应用程序在某些情况下需要处理比较复杂的逻辑, 如果只有一个线程去处理,就会导致窗口卡顿,无法处理用户的相关操作。这种情况下就需要使用多线程,其中一个线程处理窗口事件,其他线程进行逻辑运算,多个线程各司其职,不仅可以提高用户体验还可以提升程序的执行效率。

Qt中使用多线程需要注意:

  1. Qt的默认线程为窗口线程(主线程):负责窗口事件处理或窗口控件数据的更新;
  2. 子线程负责后台的业务逻辑,子线程不能对窗口对象做任何操作,这些事交给窗口线程;
  3. 主线程和子线程之间如果进行数据的传递,需要使用信号槽机制

2.线程类QThread

Qt 中提供了一个线程类,通过这个类就可以创建子线程了,Qt 中一共提供了两种创建子线程的方式。

类中常用API函数:

//QThead类常用 API
//构造函数
QThread::QThread(QObject *parent = Q_NULLPTR);
//判断线程中的任务是否处理完毕
bool QThread::isFinished() const;
//判断子线程是不是在执行任务
bool QThread::isRunning() const;

//Qt 可先设置线程优先级
//获取当前线程优先级
Priority QThread::priority() const;
//设置优先级
void QThread::setPriority(Priority priority);   //枚举类型



//退出线程的工作函数
void QThread::exit(int returnCode = 0);
//调用线程退出函数之后,线程不会马上退出,因为当前任务可能没有完成
//等待任务完成后退出线程,通常在exit() 后调用这个函数
bool QThread::wait(unsigned long time = ULONG_MAX);

优先级:
QThread::IdlePriority --> 最低的优先级
QThread::LowestPriority
QThread::LowPriority
QThread::NormalPriority
QThread::HighPriority
QThread::HighestPriority
QThread::TimeCriticalPriority
QThread::InheritPriority --> 最高的优先级, 默认是这个

信号槽:

// 和调用 exit() 效果是一样的
// 代用这个函数之后, 再调用 wait() 函数
[slot] void QThread::quit();
// 启动子线程
[slot] void QThread::start(Priority priority = InheritPriority);
// 线程退出, 可能是会马上终止线程, 一般情况下不使用这个函数
[slot] void QThread::terminate();

// 线程中执行的任务完成了, 发出该信号
// 任务函数中的处理逻辑执行完毕了
[signal] void QThread::finished();
// 开始工作之前发出这个信号, 一般不使用
[signal] void QThread::started();

静态函数

// 返回一个指向管理当前执行线程的QThread的指针
[static] QThread *QThread::currentThread();
// 返回可以在系统上运行的理想线程数 == 和当前电脑的 CPU 核心数相同
[static] int QThread::idealThreadCount();
// 线程休眠函数
[static] void QThread::msleep(unsigned long msecs);	// 单位: 毫秒
[static] void QThread::sleep(unsigned long secs);	// 单位: 秒
[static] void QThread::usleep(unsigned long usecs);	// 单位: 微秒

任务处理函数

// 子线程要处理什么任务, 需要写到 run() 中
[virtual protected] void QThread::run();

run()是一个虚函数,如果想让创建的子线程执行某个任务,需要写一个子类让其继承QThread,并且在子类中重写父类的run()方法,函数体就是任务处理流程。

run()函数是受保护的成员函数,不能类外调用,如果需要通过当前线程对象调用槽函数start()启动子线程,启动后run()函数在线程内部被调用。

3.多线程使用:方式一

1.创建一个线程类的子对象,继承QThread

class MyThrad:public QThread
{
	......
}

2.重写父类的run()方法,在该函数内部编写子线程要处理的具体业务流程

class MyThread:public QThread
{
	protected:
	void run()
	{
		.........;
	}
}

3.在主线程中创建子线程对象,new一个

MyThread * subThread = new MyThrad;

4.启动子线程,调用start()方法

subThread->start();

不能在类的外部调用run()方法启动子对象,在外部使用start()相当于run()

创建出子线程后,父子线程之间的通信可以通过信号槽的方式,注意:

Qt子线程对象不能操作窗口类型对象,只有主线程才能操作窗口对象。

5.释放线程资源,使用信号槽机制,窗口关闭时释放

connect(this,&QWidget::destroyed,this,[=](){
  //t1 为创建的子线程对象
  gen->quit();
  gen->wait();
  gen->deleteLater();
});

4.多线程使用:方式二

Qt提高的第二种线程的创建方式弥补了第一种的缺点==,使用更加灵活,单写起来相对复杂一些==。

具体操作步骤:

1.创建一个新的类,QObject派生

class Mywork :public QObject
{
	....;
}

2.类中添加一个公有的自定义成员函数,函数体就是子线程中执行的业务逻辑

class Mywork :public 	QObject
{
public:
	//自定义函数名、参数
	void working();
}

3.主线程中创建一个 QThread 对象,就是子线程对象

QThread  *sub = new QThread ;

4.在主线程中创建工作的类对象,不要给创建的对象指定父对象

// MyWork* work = new MyWork(this);    
Mywork* work = new Mywork;          // ok

5.将Mywork对象移动到创建的子线程对象中,需要调用QObject类提供的 moveToThread() 方法

/ 如果给work指定了父对象, 这个函数调用就失败了
// 提示: QObject::moveToThread: Cannot move objects with a parent
work->moveToThread(sub);	// 移动到子线程中工作s  

启动子线程,调用 start(), 这时候线程启动了,但是移动到线程中的对象并没有工作

调用 MyWork 类对象的工作函数,让这个函数开始执行,这时候是在移动到的那个子线程中运行的

6.释放线程资源,使用信号槽机制,窗口关闭时释放

//信号槽释放资源
connect(this,&QWidget::destroyed,this,[=](){
 	 //释放创建的子线程对象   
 	 ti->quit();
 	 t1->wait();
  t1->deleteLater();
  
  //释放创建的任务对象
  gen->deleteLater();
  
});

5.Qt 线程池的使用

我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题:如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。

线程池的组成:

**1.任务队列:**储存需要处理的任务,由工作的线程来处理这些任务

​ 通过线程池提供的API函数,将一个待处理的任务添加到任务列表,或者从任务列表队伍中删除,已处理的任务会被从任务队列中删除

​ 线程池的使用者,及往任务队列添加任务的线程就是生产者线程

2.工作的线程: (任务队列中的消费者),N个

​ 线程池中维护一定数量的工作线程,作用是不停的读任务队列,从里面取出任务并处理

如果任务队列为空,工作的线程将会被阻塞(使用条件变量/信号量阻塞)

​ 如果阻塞之后有了新的任务,由生产者将阻塞解除,工作线程开始工作

2.管理者线程: (不处理任务队列中的任务),1个

​ 作用:周期性的对任务队列中的任务数量以及处于忙状态的工作线程个数进行检测

​ 任务过多时,适当创建新的工作线程

​ 任务过少时,适当销毁一些工作线程

QRunnable

使用线程池需要先创建任务,添加到线程池中的任务 需要QRunnable类型因此需要创建子类继承QRunnable类,然后重写run()方法,在该函数编写在线程池中执行的任务,并将子类对象传递给线程池,这样线程就可以被线程池中的某个工作线程处理掉。

QRunnable常用函数:

// 在子类中必须要重写的函数, 里边是任务的处理流程
[pure virtual] void QRunnable::run();

// 参数设置为 true: 这个任务对象在线程池中的线程中处理完毕, 这个任务对象就会自动销毁
// 参数设置为 false: 这个任务对象在线程池中的线程中处理完毕, 对象需要程序猿手动销毁
void QRunnable::setAutoDelete(bool autoDelete);
// 获取当前任务对象的析构方式,返回true->自动析构, 返回false->手动析构
bool QRunnable::autoDelete() const;

1.创建一个要添加到线程池中的任务类

class MyWork:public QObject , public QRunnable
{
	Q_OBJECT
public:
	explicit MyWork (QObject *parent = nullptr)
	{
		//执行任务完毕,该对象自动销毁
		setAutoDelete(true);
	}
~MyWork();

void run() override();
}

在上面的示例中 MyWork 类是一个多重继承,如果需要在这个任务中使用 Qt 的信号槽机制进行数据的传递就必须继承 QObject 这个类,如果不使用信号槽传递数据就可以不继承了,只继承 QRunnable 即可。

QThreadPool

Qt 中的 QThreadPool 类管理了一组 QThreads, 里边还维护了一个任务队列。QThreadPool 管理和回收各个 QThread 对象,以帮助减少使用线程的程序中的线程创建成本。==每个Qt应用程序都有一个全局 QThreadPool 对象,可以通过调用 globalInstance() 来访问它。==也可以单独创建一个 QThreadPool 对象使用。

线程池常用API函数:

// 获取和设置线程中的最大线程个数
int maxThreadCount() const;
void setMaxThreadCount(int maxThreadCount);

// 给线程池添加任务, 任务是一个 QRunnable 类型的对象
// 如果线程池中没有空闲的线程了, 任务会放到任务队列中, 等待线程处理
void QThreadPool::start(QRunnable * runnable, int priority = 0);
// 如果线程池中没有空闲的线程了, 直接返回值, 任务添加失败, 任务不会添加到任务队列中
bool QThreadPool::tryStart(QRunnable * runnable);

// 线程池中被激活的线程的个数(正在工作的线程个数)
int QThreadPool::activeThreadCount() const;

// 尝试性的将某一个任务从线程池的任务队列中删除, 如果任务已经开始执行就无法删除了
bool QThreadPool::tryTake(QRunnable *runnable);
// 将线程池中的任务队列里边没有开始处理的所有任务删除, 如果已经开始处理了就无法通过该函数删除了
void QThreadPool::clear();

// 在每个Qt应用程序中都有一个全局的线程池对象, 通过这个函数直接访问这个对象
static QThreadPool * QThreadPool::globalInstance();

runnable);

// 线程池中被激活的线程的个数(正在工作的线程个数)
int QThreadPool::activeThreadCount() const;

// 尝试性的将某一个任务从线程池的任务队列中删除, 如果任务已经开始执行就无法删除了
bool QThreadPool::tryTake(QRunnable *runnable);
// 将线程池中的任务队列里边没有开始处理的所有任务删除, 如果已经开始处理了就无法通过该函数删除了
void QThreadPool::clear();

// 在每个Qt应用程序中都有一个全局的线程池对象, 通过这个函数直接访问这个对象
static QThreadPool * QThreadPool::globalInstance();


一般情况下,我们不需要在 Qt 程序中创建线程池对象,**直接使用 Qt 为每个应用程序提供的线程池全局对象即可。得到线程池对象之后,调用 start() 方法**就可以将一个任务添加到线程池中,这个任务就可以被线程池内部的线程池处理掉了,使用线程池比自己创建线程的这种多种多线程方式更加简单和易于维护。