一、主线程
每个应用程序启动后,拥有的第一个线程称为主线程。QT中创建的第一个桌面应用程序(QDialog、QMainwindow or QWidget)开始运行,那么这个桌面应用程序就是主线程,也成为GUI线程。另外,子线程即工作线程,主要负责处理GUI线程的耗时工作(耗时工作例如,开启视频、打开图片的同时,通过window串口向GUI界面传输数据),QT中所有的组件类和几个相关的类只能工作在GUI线程,不能工作在子线程。
二、子线程
QThread线程类是Qt线程中一个公共的线程类,所有的线程类都是从QThread线程中派生出来,派生的线程需要重写虚函数run,通过在主线程中启动子线程来调用子线程的run函数。
QThread类提供不依赖于平台的管理线程的方法,一个QThread类的对象管理一个线程,一般从QThread继承一个自定义类,并重定义虚函数run,在run函数里实现线程需要完成的任务。
将应用程序的线程称为主线程,额外创建的线程称为工作线程,一般在主线程里创建工作线程,并调用start()开始执行工作线程的任务。start()会在内部调用run函数,进入工作线程的事件循环,在run函数里调用exit()或quit()可以结束线程的事件循环,或在主线程里调用terminate()强制结束线程。
子线程创建的两种方法:
(1)继承自QThread类,需要重写虚函数run
a.自定义一个线程类,继承自QThread
b.在主线程里面对这个类进行实例化
c.在主线程里面启动子线程
d.主线程和子线程通过信号与槽进行通信
(2)将新建的类放到线程中去运行----->moveToThread(),不需要重写虚函数run
a.用户自己创建一个类
b.在主线程里面去实例化该类
c.将该类放到我们的线程中去运行,moveToThread
d.启动线程
e.主线程和子线程通过信号与槽机制进行通信
三、多线程
一个应用程序一般只有一个线程,一个线程内的操作是顺序执行的,如果有某个比较消耗时间的计算或操作,比如网络通信中的文件传输,在一个线程内操作时,用户界面就可能会冻结而不能及时响应。在这种情况下,可以创建一个单独的线程类执行比较消耗时间的操作,并与主线程之间处理好同步与数据交互,这就是多线程应用程序。
Qt为多线程的操作提供了完整的支持,QThread是线程类,是实现多线程操作的核心类,一般从QThread继承定义自己的线程类,线程之间的同步是其交互的主要问题,Qt提供了QMutext、QMutexLocker、QReadWriteLock、QwaitCondition、QSemaphore等多种类用于实现线程之间的同步
多线程的概念:最简单的理解就是有多个线程,用户可以自行创建多个线程,但是必须要有一个主线程。
多线程的本质:
(1)并发性,并发性是多线程的本质特征
(2)在宏观上,所有线程并行执行
(3)多线程之间相互独立,互不干涉
多线程的声明周期:
新建:从新建一个线程对象到start之前的状态,都是新建状态
就绪:线程调用start之后,就处于就绪状态,等待线程调度器的调度
运行:就绪状态下的线程在获取CPU资源后就可以执行run(),此时的线程就处于运行状态,运行的线程可变为就绪、阻塞及死亡三种状态
等待/阻塞/睡眠:在一个线程执行了sleep(睡眠)、suspend(挂起)等方法,从而进入阻塞状态,在睡眠结束后方可进入就绪状态
终止:run()方法完成后或发生其它终止条件时就会切换到终止状态
四、线程的同步与互斥
1、线程同步基础
同步:
是指散布在不同任务之间的若干程序片段,它们的运行必须严格按照规定的某种先后次序来运行,这种先后次序依赖于要完成的特定的任务。例如,两个或两个以上的进程或线程在运行过程中协同步调,按预定的先后次序运行。比如任务A的运行依赖于B任务产生的数据(故这时候产生了一个内置的顺序即由B到A)
同步:完成同一任务的线程之间,需要协调它们的工作而相互等待、交互
2、线程互斥基础
互斥:
是指散布在不同任务之间的若干程序片段,当某个任务运行其中一个程序片段时,其它任务就不能运行它们之中的任一程序片段,只能等到该任务运行完成这个程序片段后才可以运行,例如,一个公共资源,同时被A和B两个线程访问,这时候就不能出现同时访问该公共资源,只有等其中一个线程访问完成之后,另一个线程才可以访问该资源。
(1)临界资源
每次只能允许一个线程进行访问的资源,称为临界资源。例如,对于一个公共厕所的一个蹲位,每次只能允许一个人进入该蹲位上厕所。
(2)线程互斥
在多线程中,多个线程在同一时刻,同时访问临界资源,这时候我们就成为线程互斥。例如,对于一个公共厕所的一个蹲位,前一个人上完厕所之后,该厕所就被空出来了,而后,无序的人群都想要去上厕所,这时候就导致了一个资源被多个人同时访问,故在线程中形成互斥。
(3)互斥锁(QMutex)
针对线程互斥而提出的一种多个线程访问同一资源的问题,在资源中进行加锁操作(lock),加锁操作后,如果这时候有两个线程同时访问同一资源,就会出现什么情况呢?这时候会出现,如果线程1访问到了该资源,线程2就必须等待线程1将资源访问完成之后,再去访问该资源,而不是与线程1进行资源的抢夺(当然,对资源的抢夺是随机的,有可能线程1先抢到资源,线程2后抢到资源,也有可能是线程2先抢到资源,线程1后抢到资源)。例如,对于一个公共厕所的一个蹲位,现在后面有多个人想要获得这个蹲位,进行上厕所操作,这时候就出现互斥的情况。解决这种办法就是读该厕所安装一把锁(即互斥锁),路人1上厕所时,就把该厕所反锁住,路人2、路人2、路人4等等都无法进入该厕所上厕所,就只能等路人1厕所上完之后,将该厕所给空出来,后面的路人2、路人3等才能进行上厕所。注意互斥锁lock和unlock(trylock和unlock)必须一起用才能有效果。
dialog.h
#ifndef DIALOG_H
#define DIALOG_H
#include <QDialog>
#include <QMutex>
#include <QThread>
namespace Ui {
class Dialog;
}
//第一个线程
class Thread1 : public QThread
{
public:
void run();//thread1的运行虚函数
};
//第二个线程
class Thread2 : public QThread
{
public:
void run();//thread2的运行虚函数
};
class Dialog : public QDialog
{
Q_OBJECT
public:
explicit Dialog(QWidget *parent = nullptr);
~Dialog();
static void func1();
static void func2();
static QMutex mutex;
private:
Ui::Dialog *ui;
Thread1 *thread1;
Thread2 *thread2;
static int number; //定义一个资源
};
#endif // DIALOG_H
dialog.cpp
#include "dialog.h"
#include "ui_dialog.h"
#include <QDebug>
Dialog::Dialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::Dialog)
{
ui->setupUi(this);
// func1();//函数正常调用,执行number
// func2();//函数正常调用,执行number
//在主线程中实例化子线程
thread1 = new Thread1;
thread2 = new Thread2;
//在主线程中开启子线程的运行
thread1->start();//开启子线程1的运行
thread2->start();//开启子线程2的运行
// thread1->exit();
// thread2->exit();
}
Dialog::~Dialog()
{
delete ui;
}
void Dialog::func1()
{
mutex.lock();
qDebug()<<"线程one已经运行";
number +=50; //number=50
qDebug()<<"func1中的第一个number="<<number;
qDebug()<<"thread1已经运行";
number -=10; //number=40
qDebug()<<"func1中的第二个number="<<number;
qDebug()<<"线程1已经运行";
mutex.unlock();
}
QMutex Dialog::mutex; //分配空间
void Dialog::func2()
{
mutex.lock();//如果不加锁的话,会出现两个线程争夺一个资源的情况
qDebug()<<"线程two已经运行";
number *=3;
qDebug()<<"func2中的第一个number="<<number;
qDebug()<<"线程thread2已经运行";
number /=2; //number=60
qDebug()<<"func2中的第二个number="<<number;
qDebug()<<"线程2已经运行";
mutex.unlock();
}
//两个子线程同时访问同一资源number
void Thread1::run()//线程1访问资源number
{
Dialog::func1();
Dialog::func2();
}
void Thread2::run()//线程2访问资源number
{
Dialog::func1();
Dialog::func2();
}
main.cpp
#include "dialog.h"
#include <QApplication>
int Dialog::number=0;
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Dialog w;
w.show();
return a.exec();
}
运行结果如下:
线程one已经运行
func1中的第一个number= 50
thread1已经运行
func1中的第二个number= 40
线程1已经运行
线程one已经运行
func1中的第一个number= 90
thread1已经运行
func1中的第二个number= 80
线程1已经运行
线程two已经运行
func2中的第一个number= 240
线程thread2已经运行
func2中的第二个number= 120
线程2已经运行
线程two已经运行
func2中的第一个number= 360
线程thread2已经运行
func2中的第二个number= 180
线程2已经运行
总结:
》》表明使用的互斥锁已经已经将资源number给锁住,thread1使用func1中的资源number时,thread2阻塞,等thread1将func1中的number资源使用完之后,thread2才使用func1中的number资源;同理,func2中的number资源也一样。