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

前言

大家好吖,欢迎来到 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.mzph.cn/web/55542.shtml

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

相关文章

[Windows]文件搜索利器Everything(附zip)

前言 写代码过程中&#xff0c;老大突然发一条信息 老大&#xff1a;这周周报发一下。 我&#xff1a;好的。 然后我就 显示桌面打开-我的电脑找到E盘&#xff0c;找到周报文件夹寻找到所有周报中今天的周报复制发送 当我用上Everything之后 打开&#xff0c;输入周报copy发…

Oracle T5-2 ILOM配置

ILOM管理口ip地址配置 连接控制器&#xff08;SP&#xff09;串口&#xff08;RJ45)&#xff0c;进行系统设置 (缺省&#xff1a;9600&#xff0c;8-n-1&#xff0c;root/changeme) …………………. ORACLESP-AK02566506 login: root Password: Detecting screen size; pl…

Axure重要元件三——中继器

亲爱的小伙伴&#xff0c;在您浏览之前&#xff0c;烦请关注一下&#xff0c;在此深表感谢&#xff01; 本节课&#xff1a;中继器 课程内容&#xff1a;认识中继器、中继器基本操作、中继器案例 应用场景&#xff1a;高级表单交互 一、认识中继器 我们不从理论视角去展示…

Android Framework AMS(05)startActivity分析-2(ActivityThread启动到Activity拉起)

该系列文章总纲链接&#xff1a;专题总纲目录 Android Framework 总纲 本章关键点总结 & 说明&#xff1a; 说明&#xff1a;本章节主要解读AMS通过startActivity启动Activity的整个流程的整个流程的第二阶段&#xff1a;从ActivityThread启动到Activity拉起。 第一阶段文…

【Vue】Vue(八)Vue3.0 使用ref 和 reactive创建响应式数据

ref 创建&#xff1a;基本类型的响应式数据 **作用&#xff1a;**定义响应式变量。语法&#xff1a;let xxx ref(初始值)。**返回值&#xff1a;**一个RefImpl的实例对象&#xff0c;简称ref对象或ref&#xff0c;ref对象的value属性是响应式的。注意点&#xff1a; JS中操作…

《拿下奇怪的前端报错》:1比特丢失导致的音视频播放时长无限增长-浅析http分片传输核心和一个坑点

问题背景 在一个使用MongoDB GridFS实现文件存储和分片读取的项目中&#xff0c;同事遇到了一个令人困惑的问题&#xff1a;音频文件总是丢失最后几秒&#xff0c;视频文件也出现类似情况。更奇怪的是&#xff0c;播放器显示的总时长为无限大。这个问题困扰了团队成员几天&…

Java项目-基于Springboot的应急救援物资管理系统项目(源码+说明).zip

作者&#xff1a;计算机学长阿伟 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、ElementUI等&#xff0c;“文末源码”。 开发运行环境 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringBoot、Vue、Mybaits Plus、ELementUI工具&#xff1a;IDEA/…

工业自动化为什么依赖光耦隔离器 --- 腾恩科技

光耦合器隔离器在工业自动化中必不可少&#xff0c;可确保信号传输&#xff0c;同时保护敏感电子设备和人员免受高压影响。选择合适的光耦合器隔离器取决于对操作环境和隔离要求的了解。本文将重点介绍在为工业应用选择光耦合器隔离器时需要考虑的关键因素。 光耦合器隔离器在工…

上传图片到github上,生成链接在Typora中使用(解决Typora的md文件在分享时的丢失问题)

上传图片到github上,生成链接在Typora中使用(解决Typora的md文件在分享时的丢失问题) 在GitHub上从操作 创建一个 GitHub 仓库: 登录 GitHub,创建一个新的仓库来存储图片。 生成 GitHub 令牌: 在 GitHub 中,前往“Settings” > “Developer settings” > “Pers…

AUTOSAR_EXP_ARAComAPI的5章笔记(12)

☞返回总目录 5.4.6 方法 骨架侧的服务方法是抽象方法&#xff0c;必须由继承骨架的服务实现子类进行重写。让我们来看一下我们服务示例中的 Adjust 方法&#xff1a; /*** 对于所有输出和非空返回参数* 生成一个包含非空返回值和/或输出参数的封装结构。*/ struct AdjustOu…

UE4 材质学习笔记08(雨滴流淌着色器/雨水涟漪着色器)

一.雨滴流淌着色器 法线贴图在红色通道和绿色通道上&#xff0c;那是法线的X轴和Y轴&#xff0c;在蓝色通道中 我有个用于雨滴流淌的蒙版&#xff0c;在Alpha通道中&#xff0c;有个时间偏移蒙版。这些贴图都是可以在PS上制作做来的&#xff0c;雨滴流淌图可以直接用笔刷画出来…

永恒之蓝漏洞

MS17-010是微软于2017年3月发布的一个安全补丁&#xff0c;旨在修复Windows操作系统中的一个严重漏洞&#xff0c;该漏洞被称为“永恒之蓝”&#xff08;EternalBlue&#xff09;。这个漏洞影响了Windows的Server Message Block&#xff08;SMB&#xff09;协议&#xff0c;允许…

Java集合剖析3】ArrayList

目录 拓展 1. 在面试时如何讲解一个集合的底层&#xff1f; 2. IDEA如何查看底层源码&#xff1f; 一、ArrayList底层数据结构 二、插入方法的具体实现 三、ArrayList底层原理总结 拓展 1. 在面试时如何讲解一个集合的底层&#xff1f; 底层的数据结构。插入方法的具体实现。…

vue综合指南(六)

​&#x1f308;个人主页&#xff1a;前端青山 &#x1f525;系列专栏&#xff1a;Vue篇 &#x1f516;人终将被年少不可得之物困其一生 依旧青山,本期给大家带来Vuet篇专栏内容:vue综合指南 目录 101、Vue 框架怎么实现对象和数组的监听&#xff1f; 102、Proxy 与 Object.d…

10 分钟使用豆包 MarsCode 帮我搭建一套后台管理系统

以下是「 豆包MarsCode 体验官」优秀文章&#xff0c;作者把梦想揉碎。 十分钟使用豆包 MarsCode 搭建后台管理项目 在这个快节奏的时代&#xff0c;开发者们总是希望能够快速、高效地完成项目的搭建与开发工作。无论是初创企业还是大型公司&#xff0c;后台管理系统都是必不可…

SpringBoot1~~~

目录 快速入门 依赖管理和自动配置 修改自动仲裁/默认版本号 starter场景启动器 自动配置 修改默认扫描包结构 修改默认配置 读取application.properties文件 按需加载原则 容器功能 Configuration Import ​编辑 Conditional ImportResource 配置绑定Configur…

要在 Git Bash 中使用 `tree` 命令,下载并手动安装 `tree`。

0、git bash 安装 git(安装,常用命令,分支操作,gitee,IDEA集成git,IDEA集成gitee,IDEA集成github,远程仓库操作) 1、下载并手动安装 tree 下载 tree.exe 从 tree for Windows 官方站点 下载 tree 的 Windows 可执行文件。tree for Window&#xff1a;https://gnuwin32.source…

鸿蒙应用开发:全面认识鸿蒙系统

前言 随着智能设备的普及和物联网的发展&#xff0c;对操作系统的需求也越来越多样化。鸿蒙操作系统作为一款面向全场景的分布式操作系统&#xff0c;其适用范围非常广泛&#xff0c;从智能手机到家用电器&#xff0c;再到工业设备&#xff0c;都能找到应用场景。特别是在智能…

Nginx如何配置Gzip

Nginx 配置 Gzip 压缩可以显著减小传输的文件大小&#xff0c;提高网页加载速度。以下是在 Nginx 中配置 Gzip 的详细步骤&#xff1a; 一、找到 Nginx 配置文件 Nginx 的配置文件通常位于 /etc/nginx/nginx.conf 或 /usr/local/nginx/conf/nginx.conf&#xff08;取决于 Ngin…

鸿蒙网络编程系列22-Web组件文件上传示例

1. web组件文件上传功能简介 鸿蒙的web组件可以加载网页&#xff0c;如果网页本身具备文件上传功能的话就比较尴尬了&#xff0c;因为html上传文件时&#xff0c;允许用户选择本地文件&#xff0c;但是鸿蒙因为安全性的考虑&#xff0c;只允许操作沙箱中的文件&#xff0c;所以…