Linux -- 从抢票逻辑理解线程互斥

目录

抢票逻辑代码:

thread.hpp

thread.cc

运行结果:

为什么票会抢为负数?

概念前言

临界资源

临界区

原子性

 数据不一致

为什么数据不一致?

互斥

概念 

pthread_mutex_init(初始化互斥锁)

pthread_mutex_lock(申请互斥锁)

pthread_mutex_unlock(释放互斥锁)

pthread_mutex_destory(销毁互斥锁)

全局的互斥锁

thread.cc 代码

局部的互斥锁

thread.cc 代码


抢票逻辑代码:

thread.hpp

#ifndef __THREAD_HPP__
#define __THREAD_HPP__
#include<vector>
#include<iostream>
#include<string>
#include<functional>
#include<pthread.h>
#include<unistd.h>namespace ThreadModule
{//给 函数参数为T(T为任意类型)的引用,返回值为 void 的函数 重命名为func_ttemplate<typename T>using func_t=std::function<void(T&)>;template<typename T>class Thread{public://线程的任务void Excute(){_func(_data);}public://构造函数Thread(func_t<T> func, T &data,const std::string &name="none-name"):_func(func),_data(data),_threadname(name),_stop(true){  }//如果没有static,由于 this 指针,函数的参数有2个,而pthread_create要求函数参数只能有void*//加上static,则要求函数不能访问类内的非静态成员变量,也就避免了this指针作为函数参数        static void* threadroute(void* args){//参数从void* 类型转为Thread<T> *类型,static_cast是一种相对安全的类型转换方式Thread<T> *self=static_cast<Thread<T> *>(args);//由于没有了this指针,所以需要封装Excute函数来传递 _data参数,从而执行任务self->Excute();return nullptr;}//开始执行任务bool Start(){//创建线程int n=pthread_create(&_tid,nullptr,threadroute,this);if(!n){              _stop=false;//修改状态return true;}else{return false;}}void Detach(){//有线程启动了才分离线程if(!_stop)pthread_detach(_tid);}void Join(){if(!_stop)pthread_join(_tid,nullptr);}std::string name(){return _threadname;}void Stop(){_stop=true;}//析构函数~Thread(){  }private:std::string _threadname;//线程名bool _stop;//该线程是否启动,true表示未启动,false表示已启动pthread_t _tid;T &_data;func_t<T> _func;//线程调用的函数};
}
#endif

thread.cc

#include<iostream>
#include<pthread.h>
#include<unistd.h>
#include<string>
#include<vector>
using namespace std;
#include"thread.hpp"
using namespace ThreadModule;int g_tickets=10000;//共享资源,未保护
const int num=4;void route(int &tickets)
{//票没抢完就一直抢while(true){if(tickets>0)//还有票,可以继续抢{usleep(1000);//目前抢到的票printf("get tickets:%d\n",tickets);tickets--;}else//已经没票了,不能抢了,退出{break;}}
}int main()
{std::vector<Thread<int>> threads;//创建线程for(int i=0;i<num;i++){std::string name="thread-"+std::to_string(i+1);threads.emplace_back(route,g_tickets,name);}//启动线程for(auto &threads:threads){threads.Start();}//等待线程for(auto &threads:threads){threads.Join();std::cout<<"wait thread done, thread is: "<<threads.name()<<std::endl;}//return 0;
}

运行结果:

发现票数被减为了负数,且有的票被重复抢了,每次运行的结果都不一样。

 

为什么票会抢为负数?

概念前言

临界资源

多线程执行流共享的资源称为临界资源。

临界区

每个线程内部,访问了临界资源的代码称为临界区。

原子性

不会被任何调度机制打断的操作,该操作只有两态,要么完成,要么未完成

 数据不一致

在多线程或分布式系统中,由于并发操作或其他因素导致的数据状态不符合预期的情况。当多个线程或进程同时访问和修改共享资源时,如果没有适当的同步机制,可能会出现数据不一致的问题。这可能导致系统的不稳定、错误的结果或难以调试的行为。例如,上述的运行结果中出现了剩余的票数为负数的情况,而剩余的票数不应该出现负数,数据状态不符合预期,即数据不一致

为什么数据不一致?

我们可以来模拟一下代码的整个运行过程。

在代码中,我们创建了4个线程,每个线程都要执行以下代码,函数内中一共有 3 个地方访问了临界资源,标为1、2、3:

void route(int &tickets)
{//票没抢完就一直抢while(true){//还有票,可以继续抢if(tickets>0)//1{usleep(1000);//目前剩下的票数printf("get tickets:%d\n",tickets);//2tickets--;//3}else//已经没票了,不能抢了,退出{break;}}
}

假设现在只剩一张票了,即 g_tickets = 1

假设现在执行的是线程 1,线程 1 进行 tickets>0 的判断,这个判断过程是由 CPU 来完成的。

系统把 g_tickets 的值从内存读到 CPU 的寄存器 ebx 中,判断结果为真,线程 1 开始执行 if 的代码块,还没执行到打印操作,线程 1 被挂起并切走了,切走时线程 1 带走了寄存器的上下文数据,g_tickets 还是 1,还没有写回到内存中

此时轮到线程 2 执行函数了,线程 2 也进行了 tickets>0 的判断,由于 g_tickets 的值依旧为 1,和线程 1 的过程一样,所以线程 2 也执行 if 的代码块, 线程 2 也还没有执行到打印操作,就被挂起并切走了。线程 3 同理。

再次轮到线程 1 时,由于已经进行过 if 判断了,线程 1 直接执行打印操作,对 tickets -- 并把 tickets 的值写回到内存中,g_tickets 的值变为 0。 这里我们需要了解到,tickets-- 看似只有一句代码,其实要分为三个过程来执行:

  1. 把 tickets 从内存中读到 CPU 中;
  2. CPU 进行 -- 操作;
  3. 把 tickets 的值写回内存中。

再次轮到线程 2,因为线程 2 已经进行过 if 判断了,线程 2 以为 tickets 还是1,也直接执行打印操作,把 tickets-- 并写回到内存中,但线程 2 在进行 tickets -- 操作时,读到的 tickets 已经是 0 了,-- 操作后,tickets 变为 -1,内存中的 g_tickets 的值变为 -1.

线程 3 也是同理,-- 后 tickets 变为 -2,写回到内存后,内存中的 g_tickets 的值变为 -2.

就这样 g_tickets 的值被减到了负数!

也就是说,多线程访问共享资源 g_tickets 时,由于共享资源 g_tickets 未被保护,且 -- 操作不是原子的在执行任何一个步骤时线程都可能被切换,导致产生了计算过程的中间状态

互斥

由于多个执行流访问全局数据的代码,所以会发生上面的问题,多个线程共享的全局数据就是临界资源,访问了全局数据的代码其实就是临界区,换句话说,保护临界区,就可以保护临界资源,就可以解决上面数据不一致的问题。

概念 

任何时刻,互斥保证有且只有一个执行流进入临界区,访问临界资源,对临界资源起保护作用。

pthread_mutex_init(初始化互斥锁)

#include <pthread.h>int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);

初始化一个互斥锁。 

mutex:指向要初始化的互斥锁对象的指针

attr:指向互斥锁属性对象的指针,可以为NULL以使用默认属性。

pthread_mutex_lock(申请互斥锁)

#include <pthread.h>int pthread_mutex_lock(pthread_mutex_t *mutex);

尝试获取互斥锁。如果锁已经被其他线程持有,则当前线程将被阻塞,直到锁可用。

mutex:指向要锁定的互斥锁对象的指针

pthread_mutex_unlock(释放互斥锁)

#include <pthread.h>int pthread_mutex_unlock(pthread_mutex_t *mutex);

释放一个互斥锁,允许其他等待的线程获取该锁

mutex:指向要解锁的互斥锁对象的指针

pthread_mutex_destory(销毁互斥锁)

#include <pthread.h>int pthread_mutex_destroy(pthread_mutex_t *mutex);

销毁一个互斥锁,释放相关资源。 

mutex:指向要销毁的互斥锁对象的指针

加锁

注意加锁应该精细,只需要在临界区加锁,非临界区不需要加锁! 加锁成功后,只有申请到互斥锁的线程才可以访问临界区,其他线程阻塞等待,直到锁释放后,其他线程成功竞争到互斥锁,才可以访问临界区!

全局的互斥锁

如果互斥锁是全局的,或者静态的,则不需要 init 和 destory

thread.cc 代码

#include<iostream>
#include<pthread.h>
#include<unistd.h>
#include<string>
#include<vector>
using namespace std;
#include"thread.hpp"
using namespace ThreadModule;int g_tickets=10000;//共享资源,未保护
const int num=4;//一把全局的锁
pthread_mutex_t gmutex=PTHREAD_MUTEX_INITIALIZER;void route(int &tickets)
{//票没抢完就一直抢while(true){pthread_mutex_lock(&gmutex);//申请锁//还有票,可以继续抢if(tickets>0)//1{usleep(1);//目前剩下的票数printf("get tickets:%d\n",tickets);//2tickets--;//3pthread_mutex_unlock(&gmutex);//释放锁}else//已经没票了,不能抢了,退出{pthread_mutex_unlock(&gmutex);//释放锁break;}}
}int main()
{std::vector<Thread<int>> threads;//创建线程for(int i=0;i<num;i++){std::string name="thread-"+std::to_string(i+1);threads.emplace_back(route,g_tickets,name);}//启动线程for(auto &threads:threads){threads.Start();}//等待线程for(auto &threads:threads){threads.Join();std::cout<<"wait thread done, thread is: "<<threads.name()<<std::endl;}//return 0;
}

不再出现抢到负数的票和抢到重复的票的情况了,且每次运行结果都一样:

局部的互斥锁

thread.cc 代码

#include<iostream>
#include<pthread.h>
#include<unistd.h>
#include<string>
#include<vector>
#include<mutex>
using namespace std;
#include"thread.hpp"
using namespace ThreadModule;int g_tickets=10000;//共享资源,未保护
const int num=4;//因为锁变成局部的,为了让route可以访问互斥锁,且统计每个线程抢到了多少张票,定义一个类
class ThreadData
{
public:ThreadData(int &tickets,std::string name,pthread_mutex_t &mutex):_tickets(tickets),_name(name),_mutex(mutex),_total(0){ }~ThreadData(){   }
public:int &_tickets;//所有线程最终都会引用同一个全局变量g_ticketsstd::string _name;int _total;pthread_mutex_t &_mutex;
};void route(ThreadData *td)
{//票没抢完就一直抢while(true){pthread_mutex_lock(&td->_mutex);//申请锁//还有票,可以继续抢if(td->_tickets>0)//1{usleep(1);//目前剩下的票数printf("%s running, get tickets:%d\n",td->_name.c_str(),td->_tickets);//2td->_tickets--;//3td->_total++;pthread_mutex_unlock(&td->_mutex);//释放锁}else//已经没票了,不能抢了,退出{pthread_mutex_unlock(&td->_mutex);//释放锁break;}}
}int main()
{//一把局部的锁pthread_mutex_t mutex;pthread_mutex_init(&mutex,nullptr);//初始化互斥锁std::vector<Thread<ThreadData*>> threads;std::vector<ThreadData*> datas;//创建线程for(int i=0;i<num;i++){std::string name="thread-"+std::to_string(i+1);ThreadData* td = new ThreadData(g_tickets,name,mutex);threads.emplace_back(route,td,name);datas.emplace_back(td);}//启动线程for(auto &threads:threads){threads.Start();}//等待线程for(auto &threads:threads){threads.Join();//std::cout<<"wait thread done, thread is: "<<threads.name()<<std::endl;}for(auto data:datas){std::cout<<data->_name<<" : "<<data->_total<<std::endl;delete data;}pthread_mutex_unlock(&mutex);//return 0;
}

封装成类

LockGuard.hpp 代码

#ifndef __LOCK_GUARD_HPP__
#define __LOCK_GUARD_HPP__
#include<pthread.h>
#include<iostream>
class LockGuard
{
public:LockGuard(pthread_mutex_t *mutex):_mutex(mutex){pthread_mutex_lock(_mutex);//加锁}~LockGuard(){pthread_mutex_unlock(_mutex);//解锁}
private:pthread_mutex_t *_mutex;
};#endif

thread.cc 代码

#include<iostream>
#include<pthread.h>
#include<unistd.h>
#include<string>
#include<vector>
#include<mutex>
using namespace std;
#include"thread.hpp"
#include"LockGuard.hpp"
using namespace ThreadModule;int g_tickets=10000;//共享资源,未保护
const int num=4;//因为锁变成局部的,为了让route可以访问互斥锁,且统计每个线程抢到了多少张票,定义一个类
class ThreadData
{
public:ThreadData(int &tickets,std::string name,pthread_mutex_t &mutex):_tickets(tickets),_name(name),_mutex(mutex),_total(0){ }~ThreadData(){   }
public:int &_tickets;//所有线程最终都会引用同一个全局变量g_ticketsstd::string _name;int _total;pthread_mutex_t &_mutex;
};void route(ThreadData *td)
{//票没抢完就一直抢while(true){LockGuard guard(&td->_mutex);//临时对象//还有票,可以继续抢if(td->_tickets>0)//1{usleep(1);//目前剩下的票数printf("%s running, get tickets:%d\n",td->_name.c_str(),td->_tickets);//2td->_tickets--;//3td->_total++;}else//已经没票了,不能抢了,退出{break;}}
}int main()
{//一把局部的锁pthread_mutex_t mutex;pthread_mutex_init(&mutex,nullptr);//初始化互斥锁std::vector<Thread<ThreadData*>> threads;std::vector<ThreadData*> datas;//创建线程for(int i=0;i<num;i++){std::string name="thread-"+std::to_string(i+1);ThreadData* td = new ThreadData(g_tickets,name,mutex);threads.emplace_back(route,td,name);datas.emplace_back(td);}//启动线程for(auto &threads:threads){threads.Start();}//等待线程for(auto &threads:threads){threads.Join();//std::cout<<"wait thread done, thread is: "<<threads.name()<<std::endl;}for(auto data:datas){std::cout<<data->_name<<" : "<<data->_total<<std::endl;delete data;}pthread_mutex_unlock(&mutex);//return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/65050.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

浅谈目前我开发的前端项目用到的设计模式

浅谈目前我开发的前端项目用到的设计模式 前言 设计模式很多&#xff0c;看到一个需求&#xff0c;项目&#xff0c;我们去开发的时候&#xff0c;肯定是做一个整体的设计进行开发&#xff0c;而在这次我项目中&#xff0c;我也做了一个整体的设计&#xff0c;为什么要设计&a…

ubuntu笔记

1.系统下载与虚拟机设置 系统下载https://cn.ubuntu.comhttps://releases.ubuntu.com 虚拟机设置: 桥接模式 在桥接模式下, 虚拟出来的操作系统就像是局域网中的一台独立的主机, 它可以访问网内任何一台机器主机网卡和虚拟网卡的IP地址处于同一个网段, 子网掩码、网关、DNS等…

开放世界目标检测 Grounding DINO

开放世界目标检测 Grounding DINO flyfish Grounding DINO 是一种开创性的开放集对象检测器&#xff0c;它通过结合基于Transformer的检测器DINO与基于文本描述的预训练技术&#xff0c;实现了可以根据人类输入&#xff08;如类别名称或指代表达&#xff09;检测任意对象的功…

【基础篇】1. JasperSoft Studio编辑器与报表属性介绍

编辑器介绍 Jaspersoft Studio有一个多选项卡编辑器&#xff0c;其中包括三个标签&#xff1a;设计&#xff0c;源代码和预览。 Design&#xff1a;报表设计页面&#xff0c;可以图形化拖拉组件设计报表&#xff0c;打开报表文件的主页面Source&#xff1a;源代码页码&#xff…

电子应用设计方案71:智能客厅窗帘系统设计

智能客厅窗帘系统设计 一、引言 智能客厅窗帘系统为用户提供了更加便捷、舒适和个性化的窗帘控制方式&#xff0c;提升了家居的智能化水平和生活品质。 二、系统概述 1. 系统目标 - 实现客厅窗帘的自动开合控制&#xff0c;可通过多种方式操作。 - 能够根据时间、光照强度和用…

免杀对抗—Behinder魔改流量特征去除

前言 在现实的攻防中&#xff0c;往往webshell要比主机后门要用得多&#xff0c;因为我们首先要突破的目标是网站嘛&#xff0c;而且waf也往往会更注重webshell的检测。webshell的免杀分为两个&#xff0c;一是静态查杀&#xff0c;二是流量查杀。静态查杀不用多说了&#xff…

高阶:基于Python paddleocr库 提取pdf 文档高亮显示的内容

预览 第1步&#xff1a;理解基本结构和导入必要的库 # 1. 首先导入需要的库 import os # 用于处理文件和路径 import cv2 # 用于图像处理 import numpy as np # 用于数值计算 from paddleocr import PaddleOCR # 用于文字识别 from pdf2image import convert_from_path #…

图书馆预约占座系统:数据驱动的座位分配机制

2.1 Java语言 Java语言是目前最流行的语言之一&#xff0c;不仅可以做桌面窗口形式的程序&#xff0c;还可以做浏览器访问的程序&#xff0c;目前最流行的就是用Java语言作为基础&#xff0c;做各种程序的后台处理。Java语言是操作变量的语言&#xff0c;而变量则是Java对于数据…

如何查看pad的console输出,以便我们更好的进行调试,查看并了解实际可能的问题。

1、以下是baidu AI回复&#xff1a; 2、说明&#xff1a; 1&#xff09;如果小伙伴们经常做android开发的话&#xff0c;这个不陌生&#xff0c;因为调试都是要开启这个开发者模式。并启用USB调试模式。 2&#xff09;需要连上USB线&#xff0c;有的时候会忘记&#xff0c;然…

GitHub 桌面版配置 |可视化界面进行上传到远程仓库 | gitLab 配置【把密码存在本地服务器】

&#x1f947; 版权: 本文由【墨理学AI】原创首发、各位读者大大、敬请查阅、感谢三连 &#x1f389; 声明: 作为全网 AI 领域 干货最多的博主之一&#xff0c;❤️ 不负光阴不负卿 ❤️ 文章目录 桌面版安装包下载clone 仓库操作如下GitLab 配置不再重复输入账户和密码的两个方…

react中使用ResizeObserver来观察元素的size变化

在 React 中使用 ResizeObserver 来观察元素的大小变化&#xff0c;可以通过创建一个自定义 Hook 来封装 ResizeObserver 的逻辑&#xff0c;并在组件中使用这个 Hook。以下是一个完整的示例&#xff0c;展示了如何在 React 中使用 ResizeObserver 来观察元素的大小变化。 自定…

智慧社区电子商务系统:实现社区资源的数字化管理

2.1vue技术 Vue (读音 /vjuː/&#xff0c;类似于 view) 是一套用于构建用户界面的渐进式JavaScript框架。 [5] 与其它大型框架不同的是&#xff0c;Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层&#xff0c;不仅易于上手&#xff0c;还便于与第三方库或既有项…

【Rust自学】6.2. Option枚举

喜欢的话别忘了点赞、收藏加关注哦&#xff0c;对接下来的教程有兴趣的可以关注专栏。谢谢喵&#xff01;(&#xff65;ω&#xff65;) 6.2.1. 什么是Option枚举 它定义于标准库中&#xff0c;在Prelude&#xff08;预导入模块&#xff09;中&#xff0c;负责描述这样的场景…

【软件项目管理】-期末突击

区别常见的项目和活动 项目和活动的区别&#xff1a; 定义&#xff1a; 项目&#xff1a;为创造独特成果而进行的临时性工作。活动&#xff1a;日常运营中的重复性工作。 目标&#xff1a; 项目&#xff1a;实现特定成果&#xff0c;一次性。活动&#xff1a;维持日常运作&am…

OpenResty开发环境搭建

简介 OpenResty 是一个基于 Nginx的高性能 Web 平台&#xff0c;用于方便地搭建能够处理超高并发、扩展性极高的动态 Web 应用、Web 服务和动态网关。官方地址&#xff1a;http://openresty.org/cn/ 具备下列特点&#xff1a; 具备Nginx的完整功能基于Lua语言进行扩展&#…

突发!GitLab将停止对中国区用户提供GitLab.com账号服务

突发!GitLab将停止对中国区用户提供GitLab.com账号服务 近日,被视为全球第二大开源代码托管和项目管理平台的 GitLab 宣布其将对中国区用户停止提供 GitLab.com 账号服务,建议现有用户迁移到极狐。中国 IP 地址现在访问 GitLab.com 页面会弹出下面窗口且直接转到 about.git…

【C++】ceil 和 floor 函数的实现与分析

博客主页&#xff1a; [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: C 文章目录 &#x1f4af;前言&#x1f4af;ceil 和 floor 函数的基础介绍1. ceil 函数定义与功能示例代码输出结果功能分析使用场景 2. floor 函数定义与功能示例代码输出结果功能分析使用场景 &#x1f4af;自行实现…

合合信息:探索视觉内容安全新前沿

2024年12月13日-15日&#xff0c;中国图象图形学学会在杭州召开。大会期间&#xff0c;来自合合信息的图像算法研发总监郭丰俊进行了主题为“视觉内容安全技术的前沿进展与应用”的演讲&#xff0c;介绍了视觉内容安全问题&#xff0c;并总结了现今的技术发展&#xff0c;对我很…

【JetPack】Navigation知识点总结

Navigation的主要元素&#xff1a; 1、Navigation Graph&#xff1a; 一种新的XML资源文件,包含应用程序所有的页面&#xff0c;以及页面间的关系。 <?xml version"1.0" encoding"utf-8"?> <navigation xmlns:android"http://schemas.a…

教师如何打造专属私密成绩查询系统?

期末的校园&#xff0c;被一种特殊的氛围所笼罩。老师们如同辛勤的工匠&#xff0c;精心打磨着每一个教学环节。复习阶段&#xff0c;他们在知识的宝库中精挑细选&#xff0c;把一学期的重点内容一一梳理&#xff0c;为学生们打造出系统的复习框架。课堂上&#xff0c;他们激情…