【Linux】<互斥量>解决<抢票问题>——【多线程竞争问题】

news2024/10/18 16:28:34

前言

大家好吖,欢迎来到 YY 滴Linux系列 ,热烈欢迎! 本章主要内容面向接触过C++的老铁
主要内容含:
在这里插入图片描述

欢迎订阅 YY滴C++专栏!更多干货持续更新!以下是传送门!

  • YY的《C++》专栏
  • YY的《C++11》专栏
  • YY的《Linux》专栏
  • YY的《数据结构》专栏
  • YY的《C语言基础》专栏
  • YY的《初学者易错点》专栏
  • YY的《小小知识点》专栏
  • YY的《单片机期末速过》专栏
  • YY的《C++期末速过》专栏
  • YY的《单片机》专栏
  • YY的《STM32》专栏
  • YY的《数据库》专栏
  • YY的《数据库原理》专栏

目录

  • 一.抢票问题展示——"票数变成负数"
    • 1.问题展示:
    • 2.“ticket--”执行的过程是''非原子的'':多个线程会进入该代码段
  • 二.互斥&临界区&临界资源
  • 三.互斥量(锁)
    • 1.互斥量所需的头文件
    • 2.互斥量的初始化(动态&静态)
    • 3.互斥量的销毁
    • 4.互斥量的加锁&解锁
  • 四.<互斥量>解决<抢票问题>

一.抢票问题展示——“票数变成负数”

1.问题展示:

  • 下面代码所示
  • 我们会发现票数逐渐减少,最后甚至 减成了负数
  • 但是明明我们route函数里面设置的if ( ticket > 0 )后面才会ticket--,这是为什么?
  • 原因: ticket–的操作是 非原子性的,会被调度机制打断, 有多个线程会进入该代码段
  • 关于原子性,下面有详细分析
// 操作共享变量会有问题的售票系统代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>

int ticket = 100;//设置的剩余票数

void *route(void *arg)
{
     char *id = (char*)arg;
     while ( 1 ) {
          if ( ticket > 0 ) {
          usleep(1000);
          printf("%s sells ticket:%d\n", id, ticket);
          ticket--;
     } 
     else {
          break;
          }
     }
}
int main( void )
{
     pthread_t t1, t2, t3, t4;
     pthread_create(&t1, NULL, route, "thread 1");
     pthread_create(&t2, NULL, route, "thread 2");
     pthread_create(&t3, NULL, route, "thread 3");
     pthread_create(&t4, NULL, route, "thread 4");
     pthread_join(t1, NULL);
     pthread_join(t2, NULL);
     pthread_join(t3, NULL);
     pthread_join(t4, NULL);
}

--一次执行结果:
thread 4 sells ticket:100
...
thread 4 sells ticket:1
thread 2 sells ticket:0
thread 1 sells ticket:-1//减成负数
thread 3 sells ticket:-2

2.“ticket–”执行的过程是’‘非原子的’':多个线程会进入该代码段

  • 原子性:
  • 原子性: 不会被任何调度机制打断的操作
  • 该操作只有两态,要么 完成 ,要么 未完成
  • -- 操作并不是原子操作,而是对应 三条汇编指令
  1. load :将共享变量ticket从内存加载到寄存器中
  2. update : 更新寄存器里面的值,执行-1操作
  3. store :将新值,从寄存器写回共享变量ticket的内存地址
  • 票数变成负数原因分析 / ticket–分析:
  1. if 语句判断条件为真以后,代码可以并发的切换到其他线程
  2. usleep 这个模拟漫长业务的过程,在这个漫长的业务过程中, 可能有很多个线程会进入该代码段
  3. –ticket 操作本身就不是一个原子操作

二.互斥&临界区&临界资源

通过上述问题,我们明白:

  • 代码 必须要有互斥行为 当代码进入临界区执行时,不允许其他线程进入该临界区。

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

  • 能实现该 互斥行为 的,也就是我们下面用到的,即 互斥量

  • 如果多个线程同时要求执行临界区的代码, 任何一个时刻, 也只允许一个线程正在访问共享资源

  • 我们把我们进程中访问临界资源的代码片段,称为 临界区
    在这里插入图片描述

  • 对应上文提到抢票问题,我们也明确了共享区,以及该加锁解锁的位置,如下图所示:
    在这里插入图片描述

三.互斥量(锁)

1.互斥量所需的头文件

  • 线程库中有互斥锁
	#include <pthread.h> 
	#include <stdio.h>  

2.互斥量的初始化(动态&静态)

初始化互斥量有两种方法:静态初始化和动态初始化

  • 方法1,静态初始化:
  • 使用 PTHREAD_ MUTEX_ INITIALIZER 初始化的互斥量不需要销毁!!!
    静态初始化的互斥量不需要显式调用pthread_mutex_destroy函数进行销毁
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
	#include <pthread.h> 
	#include <stdio.h>  
	pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;  

	void* thread_func(void* arg) {  
	    pthread_mutex_lock(&mutex); // 访问共享资源  
	    // ... 执行一些操作 ...  
	    pthread_mutex_unlock(&mutex); // 释放互斥量  
	    return NULL;  
	}  
	int main() {  
	    pthread_t thread1, thread2;  
	    // 创建两个线程  
	    pthread_create(&thread1, NULL, thread_func, NULL);  
	    pthread_create(&thread2, NULL, thread_func, NULL);  
	    // 等待两个线程结束  
	    pthread_join(thread1, NULL);  
	    pthread_join(thread2, NULL);  
	    // 注意:静态初始化的互斥量不需要显式销毁  
	    return 0;  
	}
  • 方法2,动态初始化
  • 动态初始化的互斥量在使用完毕后需要显式调用pthread_mutex_destroy函数进行销毁
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
参数:
    mutex:要初始化的互斥量
    attr:指向互斥量属性对象的指针。如果传递NULL,则使用默认的互斥量属性(通常是非递归、非错误检查的)
	#include <pthread.h> 
	#include <stdio.h>  
	// 互斥量  
	pthread_mutex_t mutex; 

	void* thread_func(void* arg) {  
	    pthread_mutex_lock(&mutex); // 访问共享资源  
	    // ... 执行一些操作 ...  
	    pthread_mutex_unlock(&mutex); // 释放互斥量  
	    return NULL;  
	}  
	int main() {  
	    pthread_t thread1, thread2;  
	    // 动态初始化互斥量 
        pthread_mutex_init(&mutex, NULL); 
	    // 创建两个线程  
	    pthread_create(&thread1, NULL, thread_func, NULL);  
	    pthread_create(&thread2, NULL, thread_func, NULL);  
	    // 等待两个线程结束  
	    pthread_join(thread1, NULL);  
	    pthread_join(thread2, NULL);  
	    // 注意:动态初始化的互斥量需要显式销毁  
        // 销毁互斥量  
        pthread_mutex_destroy(&mutex); 
	    return 0;  
	}

3.互斥量的销毁

  • 销毁互斥量要注意:
  • 使用 PTHREAD_ MUTEX_ INITIALIZER 初始化的互斥量不需要销毁
  • 不要销毁一个已经加锁的互斥量
  • 已经销毁的互斥量,要确保后面不会有线程再尝试加锁
  • 销毁互斥量语法:
int pthread_mutex_destroy(pthread_mutex_t *mutex)

4.互斥量的加锁&解锁

  • 互斥量的加锁&解锁语法:
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
返回值:
    成功返回0,失败返回错误号

四.<互斥量>解决<抢票问题>

  • 现在明确了 共享区与要加锁的位置 ,也清楚了 锁(互斥量)的语法
  • 改进原来的售票系统:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <sched.h>

int ticket = 100;

pthread_mutex_t mutex;//定义全局锁

void *route(void *arg)
{
     char *id = (char*)arg;
     while ( 1 ) {
          pthread_mutex_lock(&mutex);//进入共享区前上锁
     if ( ticket > 0 ) {
          usleep(1000);
          printf("%s sells ticket:%d\n", id, ticket);
          ticket--;
          pthread_mutex_unlock(&mutex);//退出共享区时解锁
          } 
     else {
          pthread_mutex_unlock(&mutex);//退出共享区时解锁
     break;
           }
     }
}

int main( void )
{
     pthread_t t1, t2, t3, t4;
     
     pthread_mutex_init(&mutex, NULL);//锁的初始化
     
     pthread_create(&t1, NULL, route, "thread 1");
     pthread_create(&t2, NULL, route, "thread 2");
     pthread_create(&t3, NULL, route, "thread 3");
     pthread_create(&t4, NULL, route, "thread 4");
     
     pthread_join(t1, NULL);
     pthread_join(t2, NULL);
     pthread_join(t3, NULL);
     pthread_join(t4, NULL);
     
     pthread_mutex_destroy(&mutex);//销毁锁
}

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

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

相关文章

Axure树形菜单展开与折叠

亲爱的小伙伴&#xff0c;在您浏览之前&#xff0c;烦请关注一下&#xff0c;在此深表感谢&#xff01; 课程主题&#xff1a;Axure树形菜单展开与折叠 主要内容&#xff1a;树形菜单制作——层级关系——隐藏与显示——值的变化——多层交互 应用场景&#xff1a;关系树、菜…

老机MicroServer Gen8再玩 OCP万兆光口+IT直通

手上有一台放了很久的GEN8微型服务器&#xff0c;放了很多年&#xff0c;具体什么时候买的我居然已经记不清了 只记得开始装修的时候搬家出去就没用了&#xff0c;结果搬出去有了第1个孩子&#xff0c;孩子小的时候也没时间折腾&#xff0c;等孩子大一点的时候&#xff0c;又有…

MongoDB查询操作

&#x1f337;启动mongo &#x1f388;启动mongo shell &#xff08;1&#xff09;在指定目录下创建mongodb文件夹、其子文件夹data、log以及文件mongodb.log cd /home/ubuntu mkdir -p mongodb/data mkdir -p mongodb/log touch mongodb/log/mongodb.log(2)先执行mongodb命…

《计算机视觉》—— 疲劳检测

文章目录 一、疲劳检测实现的思想二、代码实现 一、疲劳检测实现的思想 了解以下几篇文章有助于了解疲劳检测的方法 基于dlib库的人脸检测 https://blog.csdn.net/weixin_73504499/article/details/142977202?spm1001.2014.3001.5501 基于dlib库的人脸关键点定位 https://blo…

基于开源Jetlinks物联网平台协议包-MQTT自定义主题数据的自动回复

目录 1.根据需要自动回复某些主题 2.调用doReply方法进行自动回复 1.根据需要自动回复某些主题 根据主题判断&#xff0c;哪些主题是需要自动回复的&#xff0c;比如设备登录&#xff0c;需要自动回复。 2.调用doReply方法进行自动回复&#xff08;代码不一定全部正确&#xf…

第 5 章:vuex

1. 理解 vuex vuex 是什么&#xff1a; 概念&#xff1a;专门在 Vue 中实现集中式状态&#xff08;数据&#xff09;管理的一个 Vue 插件&#xff0c;对 vue 应用中多个组件的共享状态进行集中式的管理&#xff08;读/写&#xff09;&#xff0c;也是一种组件间通信的方式&am…

安乃达:用CRM构建从销售到管理到售后的全链路数字化运营平台

安乃达驱动技术(上海)股份有限公司((简称&#xff1a;“安乃达”,股票代码为&#xff1a;“603350”))自2011年以来&#xff0c;公司通过多年的研发与积累现有直驱轮毂电机、减速轮毂电机和中置电机三大系列产品&#xff0c;并具备与电机相匹配的控制器、传感器、仪表等电驱动成…

springboot旧物置换网站

作者&#xff1a;计算机学长阿伟 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、ElementUI等&#xff0c;“文末源码”。 系统展示 【2024最新】基于JavaSpringBootVueMySQL的&#xff0c;前后端分离。 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;…

ThinkPHP 3.2 + Nginx 页面404问题

学习公司Callout项目时&#xff0c;发现公司项目所使用的TP版本是3.2&#xff0c;所以才可以使用例如&#xff0c;C,M,A等方法 因此我用phpEnv搭建了一个项目&#xff0c;域名为thinkphp&#xff0c;所选根目录如下 我打开网页&#xff0c;访问 thinkphp/ 和 thinkphp/index.p…

ROS 的 urdf 中 link 和 joint 的子标签中 origin 的含义

主要参考文章——主要文章&#xff0c;官方关于urdf的介绍和官方文档的翻译解析 link标签里面的origin含义 link标签里面有三个主要的子标签&#xff0c;分别是visual——连杆的外观和坐标系&#xff0c;collisoin——连杆的碰撞属性和inertial——连杆的惯性设置 首先&…

C++ | AVL树

前言 本篇博客讲解c中数据结构AVL树&#xff0c;看这篇博客之前请先去看&#xff1a;C | 二叉搜索树-CSDN博客 &#x1f493; 个人主页&#xff1a;普通young man-CSDN博客 ⏩ 文章专栏&#xff1a;C_普通young man的博客-CSDN博客 ⏩ 本人giee: 普通小青年 (pu-tong-young-m…

2024最新:零基础到精通的大模型AI产品经理全学习路线

随着人工智能技术的发展&#xff0c;尤其是大模型&#xff08;Large Model&#xff09;的兴起&#xff0c;越来越多的企业开始重视这一领域的投入。作为大模型产品经理&#xff0c;你需要具备一系列跨学科的知识和技能&#xff0c;以便有效地推动产品的开发、优化和市场化。以下…

第51期 C语言实现中断<一>

Q&#xff1a;怎样理解用C语言实现中断的过程呢&#xff1f; A&#xff1a;以下是一段使用C语言实现中断的主程序&#xff0c;和汇编语言实现中断一样也使用了定时器中断和按键中断。执行该主程序会在DE2-115的红色LED上显示流水灯&#xff0c;按下KEY1可以改变流水灯移动的…

FreeRTOS - 任务通知

1. 任务通知 所谓"任务通知"&#xff0c;你可以反过来读"通知任务"。 我们使用队列、信号量、事件组等等方法时&#xff0c;并不知道对方是谁。使用任务通知时&#xff0c;可以明确指定&#xff1a;通知哪个任务。 使用队列、信号量、事件组时&#xff…

【DBA Part01】国产Linux上安装Oracle进行数据迁移

内容如下&#xff1a; 1.1.生产环境RHEL/OEL Linux8Oracle11gR2安装配置 1.2.国产麒麟操作系统Oracle11gR2安装配置 1.3.国产麒麟操作系统Oracle11gR2 RAC集群安装配置 1.4.Oracle11gR2迁移到国产麒麟操作系统&#xff08;单机/RAC&#xff09; 本阶段课程项目需求说明&am…

[C++刷题] 基础小知识点(1) 乘方函数pow()

乘方 pow() 该函数在math.h头文件中 例如: 求圆的面积公式 s3.14*pow(r,2); 也可用于开方 pow(4,1.0/3) 注意这里要写1.0, 不然1/30,该函数会失效 例题: #include<iostream> using namespace std; #include<math.h>) int main() {int h;int r;cin >> h &g…

c++算法第3天

本篇文章包含三道算法题&#xff0c;难度由浅入深&#xff0c;适合新手练习哟 目录 第一题 题目链接 题目解析 代码原理 代码编写 本题总结 第二题 题目链接 题目解析 代码原理 代码编写 第三题 题目链接 题目解析 代码原理 代码编写 第一题 题目链接 [NOIP2…

ai抠图软件哪个好?一些快速掌握的基本抠图技巧,学习

有谁和小编一样&#xff0c;不修图还好&#xff0c;一要修图&#xff0c;100%会踩坑&#xff01; 没错&#xff0c;就是踩了网页上各种ai抠图软件免费版广告的坑&#xff0c;抠图不干净就算了&#xff0c;还会损坏原来的图片文件就很过分&#xff01; 伤心事不再多说&#xff0…

Junit单元测试时提示:Method should have no parameters

场景 Junit中运行单元测试时提示&#xff1a; Method XXX should have no parameters 如图&#xff1a; 代码如下&#xff1a; package com.ws.test.common;import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.extensi…

软考倒计时!中项计算题带练来啦!

已经进入下半年软考最后一个月&#xff0c;各位小伙伴的软考复习也已经来到了最后冲刺阶段&#xff0c;大家在知识点的背诵同时也别忽略计算题的练习&#xff0c;那么和小编一起来中项计算题的跟练吧&#xff01; 01、试题一 某信息系统项目包含如下 A、B、C、D、E&#xff0c…