c++ qt--线程(二)(第九部分)

c++ qt–线程(二)(第九部分)

一.线程并发

1.并发问题:

​ 多个线程同时操作同一个资源(内存空间、文件句柄、网络句柄),可能会导致结果不一致的问题。发生的前提条件一定是多线程下共享资源

2.写一个有并发问题的例子

1.创建一个控制台窗口

c++ qt--线程(二)(第九部分)_第1张图片

2.在main.cpp的mian函数中写下面代码
    //创建3个线程,都走相同线程函数中
    HANDLE pun1=::CreateThread(nullptr,0,&fun,nullptr,0,nullptr);
    HANDLE pun2=::CreateThread(nullptr,0,&fun,nullptr,0,nullptr);
    HANDLE pun3=::CreateThread(nullptr,0,&fun,nullptr,0,nullptr);

    if(WaitForSingleObject(pun1,INFINITE/*一直等*/)==WAIT_OBJECT_0){//如果线程正常退出
        if(pun1){
            ::CloseHandle(pun1);//关闭句柄,使用计数-1
            pun1=nullptr;
        }
    }

    if(WaitForSingleObject(pun2,INFINITE/*一直等*/)==WAIT_OBJECT_0){//如果线程正常退出
        if(pun2){
            ::CloseHandle(pun1);//关闭句柄,使用计数-1
            pun2=nullptr;
        }
    }

    if(WaitForSingleObject(pun3,INFINITE/*一直等*/)==WAIT_OBJECT_0){//如果线程正常退出
        if(pun3){
            ::CloseHandle(pun3);//关闭句柄,使用计数-1
            pun1=nullptr;
        }
    }

     qDebug()<<"-------------------count= "<
3.在main.cpp中写下面代码
int count=0;//多个线程同时操作的变量
DWORD WINAPI fun (void *p){
    for(int i=0;i<100;i++){
        count++;
        
        qDebug()<<"count= "<
4.分析结果

运行多次后,发现count变量最终的结果大多数不是300,而且输出count大多数会出现重复的值

这里是因为count++其实中间还分为几个阶段,当第一个线程存入值还没进行加操作,但是时间片结束了,这是就会到另一个线程,这里的count++是进行完整的,count存完并且加完了,然后再回到第一个线程,这时count进行加操作,这样就会出现count没加成功的情况,所以就会导致输出的count出现重复的值,count最终结果不是300

(三个线程一共执行了300次,正常count应该是300),这就是并发问题。

二.线程同步(解决并发问题)

1.线程同步的概念

线程同步就是通过协调线程执行的顺序,避免多个线程同时操作同一个资源导致并发问题,使多次执行结果一样

常见的线程同步方式:原子访问、关键段、时间、互斥量、条件变量、信号量

上面提到的并发问题,解决方法有很多,重点学习锁

2.原子访问

将上面代码进行修改

main.cpp的main函数中的代码不需要修改

对main.cpp中的其他需要修改的代码进行修改

int count=0;//多个线程同时操作的变量
DWORD WINAPI fun (void *p){
    for(int i=0;i<100;i++){
        ::InterlockedIncrement((long*)&count);//此函数使把变量按照原子操作的形式进行自增操作(++),参数要求是long*,这里进行一个强转,
        //::InterlockedDecrement((long*)&count);//此函数使把变量按照原子操作的形式进行自减操作(--),参数要求是long*,这里进行一个强转,
        qDebug()<<"count= "<

3.关键段

1.概念:

将一块代码段锁住,只有当进入这块代码段的线程走完这块代码段,其他线程才能进入该块代码段

2.将main.cpp中的需要修改的代码进行修改(用关键段去使线程同步)
CRITICAL_SECTION cs;//定义一个关键段变量

DWORD WINAPI fun (void *p){
    for(int i=0;i<100;i++){
        
        ::EnterCriticalSection(&cs);//进入关键段,加锁
       	//实现的是这一段的代码锁住
        //------------
        count++;
        count2--;
     	//-----------
        ::LeaveCriticalSection(&cs);//离开关键段解锁
        qDebug()<<"count= "<class my{
private:
    CRITICAL_SECTION cs;//定义一个关键段变量

public:
    my(){::InitializeCriticalSection(&cs);}//构造函数中写关键段的初始化
    ~my(){::DeleteCriticalSection(&cs);}//析构函数写关键段的删除

    void Lock(){//此函数写进入关键段的函数
        ::EnterCriticalSection(&cs);//上锁
    }

    void UnLock(){//此函数写离开关键段的函数
        ::LeaveCriticalSection(&cs);//解锁
    }

} LOCK;//定义类对象


DWORD WINAPI fun (void *p){
    for(int i=0;i<100;i++){
        
        LOCK.Lock();//进入关键段,加锁
       	//实现的是这一段的代码锁住
        //------------
        count++;
        count2--;
     	//-----------
        LOCK.UnLock();//离开关键段解锁
        qDebug()<<"count= "<#include 
using namespace std;
/*
1.当前类最多只能创建一个实例
2.当前这个唯一的实例,必须由当前类创建(自主创建),而不是调用者创建
3.必须向整个系统提供全局的访问点,来获取唯一的实例


懒汉式:当第一次调用这个接口函数时,先创建单例					 时间换空间的做法
饿汉式:无论是否调用获取单例接口函数,都会提前创建单例				 空间换时间的做法

*/

class CSingleton {

	CSingleton(){}
	CSingleton(const CSingleton&) = delete;//弃用拷贝构造函数
	static CSingleton* m_spring;
	~CSingleton() {}
public:
	static struct DeleteSingleton {//保证申请的堆空间一定被回收
		~DeleteSingleton() {
			if(m_spring)
				delete m_spring;
			m_spring = nullptr;
		}
	} del;//静态对象,在程序结束时静态对象会自动被回收,然后就会调用析构函数,就一定能保证申请的堆空间被回收

	//有问题的代码,在多线程下,可能会创建多个对象
	static CSingleton* CreatCSingleton() {
		//加锁
		if (!m_spring) {//如果指针为空,则创建对象
			m_spring = new  CSingleton;
		}
		//解锁
		return m_spring;
	}

	static void DestoryCSingleton(CSingleton*& psin) {
		if (m_spring)
			delete m_spring;
		m_spring = nullptr;
		psin = nullptr;
	}
    
};
CSingleton::DeleteSingleton CSingleton::del;//类外定义初始化, 格式: 类型 类名::静态变量名
CSingleton* CSingleton:: m_spring(nullptr);

int main() {

	CSingleton* p1 = CSingleton::CreatCSingleton();
	CSingleton* p2 = CSingleton::CreatCSingleton();

	cout << p1 << "   " << p2 << endl;
	CSingleton::DestoryCSingleton(p1);
	return 0;
}
2.进行测试看是否会出现在多线程下,可能会创建多个对象的问题

将上面的代码修改为下面代码

#include 
#include 
#include


//1.当前类最多只能创建一个实例
//2.当前这个唯一的实例,必须由当前类创建(自主创建),而不是调用者创建
//3.必须向整个系统提供全局的访问点,来获取唯一的实例
//
//
//懒汉式:当第一次调用这个接口函数时,先创建单例					 时间换空间的做法
//饿汉式:无论是否调用获取单例接口函数,都会提前创建单例				 空间换时间的做法


class CSingleton {

    CSingleton(){}
    CSingleton(const CSingleton&) = delete;//弃用拷贝构造函数
    static CSingleton* m_spring;
    ~CSingleton() {}
public:
    static struct DeleteSingleton {//保证申请的堆空间一定被回收
        ~DeleteSingleton() {
            if(m_spring)
                delete m_spring;
            m_spring = nullptr;
        }
    } del;//静态对象,在程序结束时静态对象会自动被回收,然后就会调用析构函数,就一定能保证申请的堆空间被回收

   
    static CSingleton* CreatCSingleton() {
     
        if (!m_spring) {//如果指针为空,则创建对象
            m_spring = new  CSingleton;
        }
       
        return m_spring;
    }

    static void DestoryCSingleton(CSingleton*& psin) {
        if (m_spring)
            delete m_spring;
        m_spring = nullptr;
        psin = nullptr;
    }


};
CSingleton::DeleteSingleton CSingleton::del;//类外定义初始化, 格式: 类型 类名::静态变量名
CSingleton* CSingleton:: m_spring(nullptr);

 //下面的5行代码是用来测试是否会出现问题的
DWORD WINAPI pun(void* p){
    Sleep(100);
    CSingleton *t=CSingleton::CreatCSingleton();//创建单例
    qDebug()<
3.用关键段进行优化
#include 
#include 
#include


//1.当前类最多只能创建一个实例
//2.当前这个唯一的实例,必须由当前类创建(自主创建),而不是调用者创建
//3.必须向整个系统提供全局的访问点,来获取唯一的实例
//
//
//懒汉式:当第一次调用这个接口函数时,先创建单例					    时间换空间的做法
//饿汉式:无论是否调用获取单例接口函数,都会提前创建单例				 空间换时间的做法


class my{
private:
    CRITICAL_SECTION cs;

public:
    my(){::InitializeCriticalSection(&cs);}
    ~my(){::DeleteCriticalSection(&cs);}

    void Lock(){
        ::EnterCriticalSection(&cs);//上锁
    }

    void UnLock(){
        ::LeaveCriticalSection(&cs);//解锁
    }

} LOCK;

class CSingleton {

    CSingleton(){}
    CSingleton(const CSingleton&) = delete;//弃用拷贝构造函数
    static CSingleton* m_spring;
    ~CSingleton() {}
public:
    static struct DeleteSingleton {//保证申请的堆空间一定被回收
        ~DeleteSingleton() {
            if(m_spring)
                delete m_spring;
            m_spring = nullptr;
        }
    } del;//静态对象,在程序结束时静态对象会自动被回收,然后就会调用析构函数,就一定能保证申请的堆空间被回收

   
    static CSingleton* CreatCSingleton() {

        //加锁
        LOCK.Lock();
        if (!m_spring) {//如果指针为空,则创建对象
            m_spring = new  CSingleton;
        }
        //解锁
        LOCK.UnLock();
        return m_spring;
    }

    static void DestoryCSingleton(CSingleton*& psin) {
        if (m_spring)
            delete m_spring;
        m_spring = nullptr;
        psin = nullptr;
    }


};
CSingleton::DeleteSingleton CSingleton::del;//类外定义初始化, 格式: 类型 类名::静态变量名
CSingleton* CSingleton:: m_spring(nullptr);


DWORD WINAPI pun(void* p){

    Sleep(100);
    CSingleton *t=CSingleton::CreatCSingleton();
    qDebug()<
4.上面代码还有能够进行优化的地方(49-59行的代码可以继续进行优化)
#include 
#include 
#include


//1.当前类最多只能创建一个实例
//2.当前这个唯一的实例,必须由当前类创建(自主创建),而不是调用者创建
//3.必须向整个系统提供全局的访问点,来获取唯一的实例
//
//
//懒汉式:当第一次调用这个接口函数时,先创建单例					    时间换空间的做法
//饿汉式:无论是否调用获取单例接口函数,都会提前创建单例				 空间换时间的做法


int count1;
class my{
private:
    CRITICAL_SECTION cs;

public:
    my(){::InitializeCriticalSection(&cs);}
    ~my(){::DeleteCriticalSection(&cs);}

    void Lock(){
        ::EnterCriticalSection(&cs);//上锁
    }

    void UnLock(){
        ::LeaveCriticalSection(&cs);//解锁
    }

} LOCK;

class CSingleton {

    CSingleton(){}
    CSingleton(const CSingleton&) = delete;//弃用拷贝构造函数
    static CSingleton* m_spring;
    ~CSingleton() {}
public:
    static struct DeleteSingleton {//保证申请的堆空间一定被回收
        ~DeleteSingleton() {
            if(m_spring)
                delete m_spring;
            m_spring = nullptr;
        }
    } del;//静态对象,在程序结束时静态对象会自动被回收,然后就会调用析构函数,就一定能保证申请的堆空间被回收

   
    static CSingleton* CreatCSingleton() {
        if (!m_spring){//如果不是空的就直接不进入上锁和解锁的流程,这样可以省时间(如果两个线程一个很快一个很慢,那慢的那个一定是非空的)
            
            //加锁
            LOCK.Lock();
            ::InterlockedIncrement((long*)&count1);//这里用一个conut1变量来进行自增,看上锁和解锁的流程要进行多少次(如果没有此判断语句,那进入上锁和解锁的流程一共要100次,看能少进入上锁和解锁的流程多少次)
            
            if (!m_spring) {//如果指针为空,则创建对象
                m_spring = new  CSingleton;
            }
            //解锁
            LOCK.UnLock();
           
        }
        return m_spring;
    }

    static void DestoryCSingleton(CSingleton*& psin) {
        if (m_spring)
            delete m_spring;
        m_spring = nullptr;
        psin = nullptr;
    }


};
CSingleton::DeleteSingleton CSingleton::del;//类外定义初始化, 格式: 类型 类名::静态变量名
CSingleton* CSingleton:: m_spring(nullptr);


DWORD WINAPI pun(void* p){

    Sleep(100);
    CSingleton *t=CSingleton::CreatCSingleton();
    qDebug()<

三.用qt中的进度条组件和四个按钮组件实现线程的五大状态(用windowsAPI实现的)

1.创建一个窗口项目

c++ qt--线程(二)(第九部分)_第2张图片

2.添加所用的组件如下

c++ qt--线程(二)(第九部分)_第3张图片

这里还用到了栅格布局

3.将每个按键组件都用qt自带的创建槽函数功能创建槽函数

c++ qt--线程(二)(第九部分)_第4张图片

4.将每个按键的功能进行实现

1.mainwindow.h中的代码如下
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include 
#include 
#include "mythread.h"
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private slots:
    void on_pushButton_3_clicked();

    void on_pushButton_clicked();

    void on_pushButton_4_clicked();

    void on_pushButton_2_clicked();
    
    void slots_setValue(int);//槽函数(用来对进度条进行操作的函数)
    
signals://信号(用来发送对进度条进行操作的信号)
    void signals_setValue(int);
    
private:
    Ui::MainWindow *ui;

public:
    HANDLE pun;//将线程变成类成员函数,使其可以在类的成员函数间进行使用
    bool m_isQuit;//看线程是否退出

public:
    Ui::MainWindow* getUI(){//公共接口,获取ui以对进度条组件创建的进度条对象进行修改
        return ui;
    }

public:
    mythread m_thread;
};
#endif // MAINWINDOW_H
2.mainwindow.cpp中的代码如下
#include "mainwindow.h"
#include "ui_mainwindow.h"


MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    m_Handle=nullptr;

    connect(this,SIGNAL(signals_setValue(int)),this,SLOT(slots_setValue(int)),Qt::QueuedConnection);//两个线程之间信号和槽的绑定需要用队列连接,不能直连
}

MainWindow::~MainWindow()
{
    delete ui;
    //这里是防止没有进行结束操作,直接关闭了程序,导致线程没有正常结束,所以这里在程序结束时,看线程有没有被关闭,如果没有关闭那么关闭
    if(m_Handle){
        ::CloseHandle(m_Handle);
    }
    m_Handle=nullptr;
}



DWORD WINAPI fun(void *p){

    MainWindow* pun=( MainWindow*)p;
    int i=0;
    while(!pun->m_isQuit){//如果线程没有结束
        
        //pun->getUI()->progressBar->setValue(i);//这里发现此方法不好使(原因应该是因为有两个线程,信号和槽用了直连的方式,如果用队列连接的方式就不会有问题了,所以我们手动使用队列连接信号和槽,然后手动发射信号,用槽函数进行处理)
        
        //发射信号
        emit pun->signals_setValue(i);
        i=++i%101;
        Sleep(80);
    }

    return 0;
}


void MainWindow::on_pushButton_3_clicked()//开始
{
    if(!m_Handle){
        m_isQuit=false;
        m_Handle=CreateThread(nullptr,0,&fun,this,0,nullptr);
    }

}


void MainWindow::on_pushButton_clicked()//暂停
{
    ::SuspendThread(m_Handle);//挂起线程
}


void MainWindow::on_pushButton_4_clicked()//恢复
{
    ::ResumeThread(m_Handle);//恢复线程
}


void MainWindow::on_pushButton_2_clicked()//退出
{
    
    m_isQuit=true;
    if(WaitForSingleObject(m_Handle,INFINITE)==WAIT_OBJECT_0){//如果线程退出了
        if(m_Handle){
            ::CloseHandle(m_Handle);
            m_Handle=nullptr;
        }
    }

}

void MainWindow::slots_setValue(int i){//槽函数(用来对进度条进行操作的函数)
    ui->progressBar->setValue(i);
}


四.用qt中的进度条组件和四个按钮组件实现线程的五大状态(使用qt里自己封装的线程库实现的)

1.创建一个窗口项目

c++ qt--线程(二)(第九部分)_第5张图片

2.添加所用的组件如下

c++ qt--线程(二)(第九部分)_第6张图片

这里还用到了栅格布局

3.将每个按键组件都用qt自带的创建槽函数功能创建槽函数

c++ qt--线程(二)(第九部分)_第7张图片

4.将每个按键的功能进行实现

1.我们要使用qt里自己封装的线程库首先要新建一个类

c++ qt--线程(二)(第九部分)_第8张图片

c++ qt--线程(二)(第九部分)_第9张图片

c++ qt--线程(二)(第九部分)_第10张图片

2.给创建的类添加父类(线程类的父类),添加线程库的头文件

c++ qt--线程(二)(第九部分)_第11张图片

3.将创建的类进行完善
1.类的头文件的代码如下
#ifndef MYTHREAD_H
#define MYTHREAD_H

#include 
#include
class mythread : public QThread
{
    Q_OBJECT//如果用到了信号槽函数,那么需要这个宏
public:
    mythread();//构造函数
    ~mythread();//析构函数
public:
    virtual void run();//虚函数,线程运行的函数
    CRITICAL_SECTION m_cs;//创建关键段(此关键段是为了在暂停的时候,循环也会因为关键段暂停,使其线程真正的暂停)

public:
    bool m_isQuit;//用来判断线程函数是否退出的变量
    bool m_isPause;//用来判断线程函数是狗暂停的变量

signals://信号函数
    void signals_emitData(int);//用来发射信号的函数
};

#endif // MYTHREAD_H

2.类的源文件的代码如下
#include "mythread.h"
#include 

mythread::mythread():m_isQuit(false),m_isPause(false)//初始化
{
    ::InitializeCriticalSection(&m_cs);//初始化关键段
}

mythread::~mythread()
{
    ::DeleteCriticalSection(&m_cs);//回收关键段
}

//qt 封装的线程库中的线程函数
void mythread::run(){//将此函数进行重写
    int i=0;
    while(!m_isQuit){//判断是否退出
        
        if(m_isPause){//判断是否暂停
             qDebug()<<"暂停";
              EnterCriticalSection(&m_cs);//进入关键段

              LeaveCriticalSection(&m_cs);//离开关键段
             continue;
        }

        //发射信号
        emit signals_emitData(i);
        i=++i%101;
        this->msleep(80);
    }
}
4.mainwindow.h中的代码如下(比用windowsAPI实现的mainwindow.h代码多了一个类对象)

此代码是在mainwindow.h的MainWindow的类中写的

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include 
#include 
#include "mythread.h"
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private slots:
    void on_pushButton_3_clicked();

    void on_pushButton_clicked();

    void on_pushButton_4_clicked();

    void on_pushButton_2_clicked();

    void slots_setValue(int);

signals:
    void signals_setValue(int);

private:
    Ui::MainWindow *ui;

public:
    HANDLE m_Handle;
    bool m_isQuit;

public:
    Ui::MainWindow* getUI(){
        return ui;
    }
//下面这两行就是多的
public:
    mythread m_thread;
};
#endif // MAINWINDOW_H

5.mainwindow.cpp中的代码如下
#include "mainwindow.h"
#include "ui_mainwindow.h"


MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    m_Handle=nullptr;

    connect(this,SIGNAL(signals_setValue(int)),this,SLOT(slots_setValue(int)),Qt::QueuedConnection);

    //线程类库中的信号 和 slots_setValue做绑定链接
        			 connect(&m_thread,SIGNAL(signals_emitData(int)),this,SLOT(slots_setValue(int)),Qt::QueuedConnection);
}

MainWindow::~MainWindow()
{
    delete ui;
    //这里是防止没有进行结束操作,直接关闭了程序,导致线程没有正常结束,所以这里在程序结束时,看线程有没有被关闭,如果没有关闭那么关闭
    if(m_Handle){
        ::CloseHandle(m_Handle);
    }
    m_Handle=nullptr;
}



DWORD WINAPI fun(void *p){

    MainWindow* pun=( MainWindow*)p;
    int i=0;
    while(!pun->m_isQuit){
        //发射信号
        emit pun->signals_setValue(i);
        i=++i%101;
        Sleep(80);
    }

    return 0;
}


void MainWindow::on_pushButton_3_clicked()//开始
{

    m_thread.m_isQuit=false;//为了下一次创建新线程能正常执行所做的操作(如果不进行此操作,那么线程刚创建就被回收了)
    
    m_thread.start(); //启动线程,执行线程函数 run

}


void MainWindow::on_pushButton_clicked()//暂停
{
    ::EnterCriticalSection(&m_thread.m_cs);//进入关键段(这里进入关键段之后,只有先离开关键段,才能再次进入关键段,以此来达到真正暂停的效果)
    
    m_thread.m_isPause=true;//暂停线程

}


void MainWindow::on_pushButton_4_clicked()//恢复
{
    m_thread.m_isPause=false;//恢复线程
    
    ::LeaveCriticalSection(&m_thread.m_cs);//离开关键段
}


void MainWindow::on_pushButton_2_clicked()//退出
{
    m_thread.m_isQuit=true;
}

void MainWindow::slots_setValue(int i){//槽函数(用来对进度条进行操作的函数)
    ui->progressBar->setValue(i);
}

你可能感兴趣的:(c++,c++,qt)