多个线程同时操作同一个资源(内存空间、文件句柄、网络句柄),可能会导致结果不一致的问题。发生的前提条件一定是多线程下共享资源
//创建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= "<
int count=0;//多个线程同时操作的变量
DWORD WINAPI fun (void *p){
for(int i=0;i<100;i++){
count++;
qDebug()<<"count= "<
运行多次后,发现count变量最终的结果大多数不是300,而且输出count大多数会出现重复的值
这里是因为count++其实中间还分为几个阶段,当第一个线程存入值还没进行加操作,但是时间片结束了,这是就会到另一个线程,这里的count++是进行完整的,count存完并且加完了,然后再回到第一个线程,这时count进行加操作,这样就会出现count没加成功的情况,所以就会导致输出的count出现重复的值,count最终结果不是300
(三个线程一共执行了300次,正常count应该是300),这就是并发问题。
线程同步就是通过协调线程执行的顺序,避免多个线程同时操作同一个资源导致并发问题,使多次执行结果一样
常见的线程同步方式:原子访问、关键段、时间、互斥量、条件变量、信号量
上面提到的并发问题,解决方法有很多,重点学习锁
将上面代码进行修改
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= "<
将一块代码段锁住,只有当进入这块代码段的线程走完这块代码段,其他线程才能进入该块代码段
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;
}
将上面的代码修改为下面代码
#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()<
#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()<
#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()<
这里还用到了栅格布局
#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
#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);
}
这里还用到了栅格布局
#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
#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);
}
}
此代码是在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
#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);
}