企业网站定制设计,淄博做网站推广哪家好,福州制作网站设计找哪家公司,seo公司 引擎c qt–线程#xff08;二#xff09;#xff08;第九部分#xff09;
一.线程并发
1.并发问题#xff1a;
多个线程同时操作同一个资源#xff08;内存空间、文件句柄、网络句柄#xff09;#xff0c;可能会导致结果不一致的问题。发生的前提条件一定是多线程下…c qt–线程二第九部分
一.线程并发
1.并发问题
多个线程同时操作同一个资源内存空间、文件句柄、网络句柄可能会导致结果不一致的问题。发生的前提条件一定是多线程下共享资源
2.写一个有并发问题的例子
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);//关闭句柄使用计数-1pun1nullptr;}}if(WaitForSingleObject(pun2,INFINITE/*一直等*/)WAIT_OBJECT_0){//如果线程正常退出if(pun2){::CloseHandle(pun1);//关闭句柄使用计数-1pun2nullptr;}}if(WaitForSingleObject(pun3,INFINITE/*一直等*/)WAIT_OBJECT_0){//如果线程正常退出if(pun3){::CloseHandle(pun3);//关闭句柄使用计数-1pun1nullptr;}}qDebug()-------------------count count;3.在main.cpp中写下面代码
int count0;//多个线程同时操作的变量
DWORD WINAPI fun (void *p){for(int i0;i100;i){count;qDebug()count count;Sleep(50);}return 0;
}4.分析结果
运行多次后发现count变量最终的结果大多数不是300而且输出count大多数会出现重复的值
这里是因为count其实中间还分为几个阶段当第一个线程存入值还没进行加操作但是时间片结束了这是就会到另一个线程这里的count是进行完整的count存完并且加完了然后再回到第一个线程这时count进行加操作这样就会出现count没加成功的情况所以就会导致输出的count出现重复的值count最终结果不是300
三个线程一共执行了300次正常count应该是300这就是并发问题。
二.线程同步解决并发问题
1.线程同步的概念
线程同步就是通过协调线程执行的顺序避免多个线程同时操作同一个资源导致并发问题使多次执行结果一样
常见的线程同步方式原子访问、关键段、时间、互斥量、条件变量、信号量
上面提到的并发问题解决方法有很多重点学习锁
2.原子访问
将上面代码进行修改
main.cpp的main函数中的代码不需要修改
对main.cpp中的其他需要修改的代码进行修改
int count0;//多个线程同时操作的变量
DWORD WINAPI fun (void *p){for(int i0;i100;i){::InterlockedIncrement((long*)count);//此函数使把变量按照原子操作的形式进行自增操作参数要求是long*,这里进行一个强转//::InterlockedDecrement((long*)count);//此函数使把变量按照原子操作的形式进行自减操作--参数要求是long*,这里进行一个强转qDebug()count count;Sleep(50);}
}3.关键段
1.概念
将一块代码段锁住只有当进入这块代码段的线程走完这块代码段其他线程才能进入该块代码段
2.将main.cpp中的需要修改的代码进行修改用关键段去使线程同步
CRITICAL_SECTION cs;//定义一个关键段变量DWORD WINAPI fun (void *p){for(int i0;i100;i){::EnterCriticalSection(cs);//进入关键段加锁//实现的是这一段的代码锁住//------------count;count2--;//-----------::LeaveCriticalSection(cs);//离开关键段解锁qDebug()count count;qDebug()count2 count2;Sleep(50);}
}int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);::InitializeCriticalSection(cs);//关键段的初始化//进行输出看结果是否符合预期如果没有关键段可能会出现重复的数这里会导致count最终结果不会到达300count2最终结果不会到达-300HANDLE 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);pun1nullptr;}}if(WaitForSingleObject(pun2,INFINITE/*一直等*/)WAIT_OBJECT_0){if(pun2){::CloseHandle(pun1);pun2nullptr;}}if(WaitForSingleObject(pun3,INFINITE/*一直等*/)WAIT_OBJECT_0){if(pun3){::CloseHandle(pun3);pun1nullptr;}}qDebug()-------------------count count;qDebug()-------------------count2 count2;::DeleteCriticalSection(cs);//关键段的删除return a.exec();
}3.将关键段进行封装优化
封装成一个类
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 i0;i100;i){LOCK.Lock();//进入关键段加锁//实现的是这一段的代码锁住//------------count;count2--;//-----------LOCK.UnLock();//离开关键段解锁qDebug()count count;qDebug()count2 count2;Sleep(50);}
}int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);//进行输出看结果是否符合预期如果没有关键段可能会出现重复的数这里会导致count最终结果不会到达300count2最终结果不会到达-300HANDLE 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);pun1nullptr;}}if(WaitForSingleObject(pun2,INFINITE/*一直等*/)WAIT_OBJECT_0){if(pun2){::CloseHandle(pun1);pun2nullptr;}}if(WaitForSingleObject(pun3,INFINITE/*一直等*/)WAIT_OBJECT_0){if(pun3){::CloseHandle(pun3);pun1nullptr;}}qDebug()-------------------count count;qDebug()-------------------count2 count2;return a.exec();
}4.用关键段优化之前单例代码中存在的问题
1.之前单例代码如下
#include iostream
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 QCoreApplication
#include windows.h
#includeQDebug//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 *tCSingleton::CreatCSingleton();//创建单例qDebug()t;//输出地址看有没有不同的地址出现以此来确认是否出现了问题
}int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);//下面的3行代码是用来测试是否会出现问题的for(int i0;i100;i)::CreateThread(nullptr,0,pun,nullptr,0,nullptr);Sleep(5000);return a.exec();
}3.用关键段进行优化
#include QCoreApplication
#include windows.h
#includeQDebug//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 *tCSingleton::CreatCSingleton();qDebug()t;}int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);for(int i0;i100;i){::CreateThread(nullptr,0,pun,nullptr,0,nullptr);}Sleep(5000);return a.exec();
}4.上面代码还有能够进行优化的地方49-59行的代码可以继续进行优化
#include QCoreApplication
#include windows.h
#includeQDebug//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 *tCSingleton::CreatCSingleton();qDebug()t;}int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);for(int i0;i100;i){::CreateThread(nullptr,0,pun,nullptr,0,nullptr);}Sleep(5000);qDebug()count1;//输出count1的值return a.exec();
}三.用qt中的进度条组件和四个按钮组件实现线程的五大状态用windowsAPI实现的
1.创建一个窗口项目 2.添加所用的组件如下 这里还用到了栅格布局
3.将每个按键组件都用qt自带的创建槽函数功能创建槽函数 4.将每个按键的功能进行实现
1.mainwindow.h中的代码如下
#ifndef MAINWINDOW_H
#define MAINWINDOW_H#include QMainWindow
#include windows.h
#include mythread.h
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACEclass MainWindow : public QMainWindow
{Q_OBJECTpublic: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_H2.mainwindow.cpp中的代码如下
#include mainwindow.h
#include ui_mainwindow.hMainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow)
{ui-setupUi(this);m_Handlenullptr;connect(this,SIGNAL(signals_setValue(int)),this,SLOT(slots_setValue(int)),Qt::QueuedConnection);//两个线程之间信号和槽的绑定需要用队列连接不能直连
}MainWindow::~MainWindow()
{delete ui;//这里是防止没有进行结束操作直接关闭了程序导致线程没有正常结束所以这里在程序结束时看线程有没有被关闭如果没有关闭那么关闭if(m_Handle){::CloseHandle(m_Handle);}m_Handlenullptr;
}DWORD WINAPI fun(void *p){MainWindow* pun( MainWindow*)p;int i0;while(!pun-m_isQuit){//如果线程没有结束//pun-getUI()-progressBar-setValue(i);//这里发现此方法不好使原因应该是因为有两个线程信号和槽用了直连的方式如果用队列连接的方式就不会有问题了所以我们手动使用队列连接信号和槽然后手动发射信号用槽函数进行处理//发射信号emit pun-signals_setValue(i);ii%101;Sleep(80);}return 0;
}void MainWindow::on_pushButton_3_clicked()//开始
{if(!m_Handle){m_isQuitfalse;m_HandleCreateThread(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_isQuittrue;if(WaitForSingleObject(m_Handle,INFINITE)WAIT_OBJECT_0){//如果线程退出了if(m_Handle){::CloseHandle(m_Handle);m_Handlenullptr;}}}void MainWindow::slots_setValue(int i){//槽函数用来对进度条进行操作的函数ui-progressBar-setValue(i);
}
四.用qt中的进度条组件和四个按钮组件实现线程的五大状态使用qt里自己封装的线程库实现的
1.创建一个窗口项目 2.添加所用的组件如下 这里还用到了栅格布局
3.将每个按键组件都用qt自带的创建槽函数功能创建槽函数 4.将每个按键的功能进行实现
1.我们要使用qt里自己封装的线程库首先要新建一个类 2.给创建的类添加父类线程类的父类添加线程库的头文件 3.将创建的类进行完善
1.类的头文件的代码如下
#ifndef MYTHREAD_H
#define MYTHREAD_H#include QThread
#includewindows.h
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 QDebugmythread::mythread():m_isQuit(false),m_isPause(false)//初始化
{::InitializeCriticalSection(m_cs);//初始化关键段
}mythread::~mythread()
{::DeleteCriticalSection(m_cs);//回收关键段
}//qt 封装的线程库中的线程函数
void mythread::run(){//将此函数进行重写int i0;while(!m_isQuit){//判断是否退出if(m_isPause){//判断是否暂停qDebug()暂停;EnterCriticalSection(m_cs);//进入关键段LeaveCriticalSection(m_cs);//离开关键段continue;}//发射信号emit signals_emitData(i);ii%101;this-msleep(80);}
}4.mainwindow.h中的代码如下(比用windowsAPI实现的mainwindow.h代码多了一个类对象)
此代码是在mainwindow.h的MainWindow的类中写的
#ifndef MAINWINDOW_H
#define MAINWINDOW_H#include QMainWindow
#include windows.h
#include mythread.h
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACEclass MainWindow : public QMainWindow
{Q_OBJECTpublic: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.hMainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow)
{ui-setupUi(this);m_Handlenullptr;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_Handlenullptr;
}DWORD WINAPI fun(void *p){MainWindow* pun( MainWindow*)p;int i0;while(!pun-m_isQuit){//发射信号emit pun-signals_setValue(i);ii%101;Sleep(80);}return 0;
}void MainWindow::on_pushButton_3_clicked()//开始
{m_thread.m_isQuitfalse;//为了下一次创建新线程能正常执行所做的操作如果不进行此操作那么线程刚创建就被回收了m_thread.start(); //启动线程执行线程函数 run}void MainWindow::on_pushButton_clicked()//暂停
{::EnterCriticalSection(m_thread.m_cs);//进入关键段(这里进入关键段之后只有先离开关键段才能再次进入关键段以此来达到真正暂停的效果)m_thread.m_isPausetrue;//暂停线程}void MainWindow::on_pushButton_4_clicked()//恢复
{m_thread.m_isPausefalse;//恢复线程::LeaveCriticalSection(m_thread.m_cs);//离开关键段
}void MainWindow::on_pushButton_2_clicked()//退出
{m_thread.m_isQuittrue;
}void MainWindow::slots_setValue(int i){//槽函数用来对进度条进行操作的函数ui-progressBar-setValue(i);
}