【Linux多线程】线程的互斥与同步(附抢票案例代码+讲解)

线程的互斥与同步
- 💫 概念引入
- ⭐️临界资源(Critical Resource):
- 🌟临界区(Critical Section):
- ✨互斥(Mutex):
- ⚡️结合代码看互斥
- ☄️ 代码逻辑
- 💥运行结果
- 🔥分析
- 🌪 pthread_mutex_t线程互斥锁
- 🌈 注意事项
💫 概念引入
在并发编程中,临界资源、临界区和互斥是关键概念,用于处理多个线程或进程对共享资源的访问问题。
⭐️临界资源(Critical Resource):
== 临界资源是指在多线程或多进程环境中,被多个线程或进程共享的资源 ==,例如共享内存、全局变量、文件等。由于多个线程或进程可以同时访问临界资源,如果不加以控制,可能会导致数据的不一致或错误的结果。因此,需要通过临界区和互斥来保护临界资源的访问。
🌟临界区(Critical Section):
== 临界区是指访问临界资源的代码段或区域 ==,在临界区中,对临界资源的访问必须是原子的,即同一时间只能有一个线程或进程在执行临界区的代码。临界区的目的是保证在任意时刻只有一个线程或进程在访问共享的临界资源,从而避免竞争条件(Race Condition)和数据不一致。
✨互斥(Mutex):
互斥是一种用于保护临界资源的同步机制。互斥是一个信号量,它在任意时刻只能被一个线程或进程持有。当一个线程或进程进入临界区时,它会尝试获取互斥锁,如果互斥锁没有被其他线程或进程持有,那么该线程或进程将获得互斥锁,并进入临界区执行代码。其他线程或进程在尝试获取互斥锁时会被阻塞,直到互斥锁被释放。当线程或进程执行完临界区的代码后,会释放互斥锁,允许其他线程或进程获取互斥锁,进入临界区执行代码。
通过使用临界区和互斥,可以确保在任意时刻只有一个线程或进程在访问临界资源,从而避免了竞争条件和数据不一致问题。在多线程或多进程的并发编程中,正确使用临界区和互斥是确保程序正确运行的重要手段。
⚡️结合代码看互斥
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include<iostream>
#include <sys/syscall.h>
using namespace std;int tickets=10000; //票数总量
//抢票逻辑
void *getTicket(void *args)
{//用于将args转换为const char*类型,并将转换结果赋值给name变量。const char *name = static_cast<const char *>(args);while (true){if(tickets>0){usleep(1000);cout << name << " 抢到了票, 票的编号: " << tickets << endl;tickets--;}else{cout << name << "] 已经放弃抢票了,因为没有了..." << endl;break;}}
}int main()
{pthread_t td1;pthread_t td2;pthread_t td3;pthread_t td4;//创建4个线程 执行抢票pthread_create(&td1,nullptr,getTicket,(void*)"td1");pthread_create(&td1,nullptr,getTicket,(void*)"td2");pthread_create(&td1,nullptr,getTicket,(void*)"td3");pthread_create(&td1,nullptr,getTicket,(void*)"td4");pthread_join(td1,nullptr);pthread_join(td2,nullptr);pthread_join(td3,nullptr);pthread_join(td4,nullptr);return 0;
}
☄️ 代码逻辑
这段代码是一个简单的多线程程序,用于模拟多人抢票的场景。代码中使用了pthread库来创建和管理线程。
tickets是一个全局变量,表示票的总数量,初始值为10000。
getTicket函数是抢票的逻辑。每个线程执行此函数,表示一个人在抢票。在函数内部,通过循环不断判断是否还有票,如果有,则输出当前抢到的票的编号,并将票数减1;如果没有了,则输出该线程已经放弃抢票。
pthread_t是一个数据类型,表示线程的ID。
pthread_create函数用于创建线程。它接受四个参数:pthread_t *thread表示接收新创建线程的ID,const pthread_attr_t *attr表示线程的属性,void *(*start_routine) (void *)表示线程的入口函数,void *arg表示传递给入口函数的参数。
pthread_join函数用于等待线程结束。它接受两个参数:pthread_t thread表示要等待的线程ID,void **retval表示线程的返回值。在此代码中,使用nullptr表示不关心线程的返回值。
usleep函数用于让线程睡眠一段时间,模拟抢票过程中的延迟。
在main函数中,创建了4个线程,分别执行getTicket函数来模拟4个人同时抢票的情况。通过pthread_create函数创建线程,并通过pthread_join函数等待线程结束,确保所有线程都执行完毕后程序才结束。
💥运行结果

🔥分析
其实代码是存在Bug的
我们试想一下,== tickets-- == 这段代码执行需要几步?
在上一篇博客中有介绍到,线程的运行可能会产生临时数据放在内存,这里的tickets就是如此,我们知道执行–操作是要经过CPU的,那么 执行该代码最少需要3步:
- 把tickets拷贝到CPU
- CPU执行–操作
- 把–后的值放回给对象!
而在此多线程执行抢票的过程中,随时可能进行线程的切换(由OS调度的),那么就会出现下面的情况:
线程A在执行–操作 (还没执行完,假设此时A已经–到50张票了,还没把结果放回给对象),此时进行线程切换,B进来执行–操作,但是B并没有拿到最新的余票信息(A没有返回),此时B拿到的就是上一次的余票信息,那么就会出现 == 资源不一致 ==问题 ,该问题是很严重的!!!所以我们要有解决的措施!!!!
- 解决方案
保证临界区的原子性 可以通过加锁来实现
🌪 pthread_mutex_t线程互斥锁
pthread_mutex_t是一个线程互斥锁,用于实现线程间的互斥访问共享资源,防止多个线程同时访问造成数据竞争和错误。
互斥锁的作用是在多线程环境下保护临界区(共享资源),只允许一个线程访问临界区,其他线程必须等待互斥锁的释放。一旦一个线程获得了互斥锁,其他线程就无法同时获得该互斥锁,只能等待。
使用pthread_mutex_t需要以下几个步骤:
初始化互斥锁:在使用互斥锁之前,需要对其进行初始化。可以使用pthread_mutex_init函数来完成初始化,也可以使用静态初始化宏PTHREAD_MUTEX_INITIALIZER。
上锁:当一个线程需要访问临界区时,首先要尝试获取互斥锁,即上锁。如果互斥锁已被其他线程占用,则当前线程会阻塞,直到互斥锁被释放。
解锁:当一个线程完成对临界区的访问后,应该释放互斥锁,即解锁。这样其他线程就可以尝试获取互斥锁并访问临界区。
销毁互斥锁:在不再需要使用互斥锁时,应该将其销毁以释放系统资源。可以使用pthread_mutex_destroy函数来销毁互斥锁。
使用互斥锁可以有效地防止多线程访问共享资源时的竞态条件问题,保证程序的正确性和稳定性。但是要注意,滥用互斥锁可能导致线程之间的竞争和性能下降,因此在设计多线程程序时需要慎重考虑互斥锁的使用。
- 加锁后代码
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include<iostream>
#include <sys/syscall.h>
using namespace std;int tickets=10000; //票数总量pthread_mutex_t mutex; //创建互斥锁
//抢票逻辑
void *getTicket(void *args)
{//用于将args转换为const char*类型,并将转换结果赋值给name变量。const char *name = static_cast<const char *>(args);while (true){pthread_mutex_lock(&mutex);//临界区加锁if(tickets>0){usleep(1000);cout << name << " 抢到了票, 票的编号: " << tickets << endl;tickets--;pthread_mutex_unlock(&mutex); //解锁 }else{cout << name << "] 已经放弃抢票了,因为没有了..." << endl;break;pthread_mutex_unlock(&mutex); //解锁 }}
}int main()
{pthread_mutex_init(&mutex, nullptr); //初始化pthread_t td1;pthread_t td2;pthread_t td3;pthread_t td4;//创建4个线程 执行抢票pthread_create(&td1,nullptr,getTicket,(void*)"td1");pthread_create(&td1,nullptr,getTicket,(void*)"td2");pthread_create(&td1,nullptr,getTicket,(void*)"td3");pthread_create(&td1,nullptr,getTicket,(void*)"td4");pthread_join(td1,nullptr);pthread_join(td2,nullptr);pthread_join(td3,nullptr);pthread_join(td4,nullptr);pthread_mutex_destroy(&mutex);//销毁锁return 0;
}
- 运行

这时候就解决了~
🌈 注意事项
- 临界区,只要对临界区加锁,而且加锁的粒度约细越好
- 加锁的本质是让线程执行临界区代码串行化
- 加锁是一套规范,通过临界区对临界资源进行访问的时候,要加就都要加
- 锁保护的是临界区, 任何线程执行临界区代码访问临界资源,都必须先申请锁,前提是都必须先看到锁!
- 这把锁,本身不就也是临界资源吗?锁的设计者早就想到了 pthread_mutex_lock: 竞争和申请锁的过程,就是原子的!
Q:为什么在if和else后面都要 执行: pthread_mutex_unlock(&mutex);来释放 直接放在循环的最后会有什么问题?
A:会造成其他在锁外的线程无线在阻塞等待 … 不解锁 不结束!

☀️ 🌤 ⛅️ 🌥 ☁️ 🌦 🌧 ⛈ 🌩 🌨 ❄️ ☃️ ⛄️ 🌬 💨 💧 💦 ☔️ ☂️ 🌊 🌫
相关文章:
【Linux多线程】线程的互斥与同步(附抢票案例代码+讲解)
线程的互斥与同步 💫 概念引入⭐️临界资源(Critical Resource):🌟临界区(Critical Section):✨互斥(Mutex): ⚡️结合代码看互斥☄️ 代码逻辑&a…...
ajax概述
目录 1.什么是ajax 2.ja原生ajax 3.jQuery框架的ajax 4.综合案例 1.什么是ajax Ajax 即"Asynchronous Javascript And XML"(异步 JavaScript 和 XML),是指一种创建交互式网页应用的网页开发技术。Ajax 异步 JavaScript 和 XML&…...
小白带你学习linux的mysql服务(主从mysql服务和读写分离三十一)
目录 二、MySQL Replication优点: 三、MySQL复制类型 1、异步复制(Asynchronous repication) 2、全同步复制(Fully synchronous replication) 3、半同步复制(Semisynchronous replication)…...
【低代码专题方案】iPaaS运维方案,助力企业集成平台智能化高效运维
01 场景背景 随着IT行业的发展和各家企业IT建设的需要,信息系统移动化、社交化、大数据、系统互联、数据打通等需求不断增多,企业集成平台占据各个企业领域,成为各业务系统数据传输的中枢。 集成平台承接的业务系统越多,集成平台…...
Android SDK 上手指南||第一章 环境需求||第二章 IDE:Eclipse速览
第一章 环境需求 这是我们系列教程的第一篇,让我们来安装Android的开发环境并且把Android SDK运行起来! 介绍 欢迎来到Android SDK入门指南系列文章,如果你想开始开发Android App,这个系列将从头开始教你所须的技能。我们假定你…...
Amazon Linux上使用ec2-user来设置开机自启动的shell脚本
要在Amazon Linux上使用ec2-user来设置开机自启动的shell脚本,可以按照以下步骤操作: 1. 确保您拥有要设置自启动的shell脚本。假设脚本的路径是/home/ec2-user/myscript.sh。 2. 使用以下命令打开/etc/rc.d/rc.local文件: shell sudo nano /…...
【Spring】Spring 下载及其 jar 包
根据 【动力节点】最新Spring框架教程,全网首套Spring6教程,跟老杜从零学spring入门到高级 以及老杜的原版笔记 https://www.yuque.com/docs/share/866abad4-7106-45e7-afcd-245a733b073f?# 《Spring6》 进行整理, 文档密码:mg9b…...
蓝桥杯2023年第十四届省赛-飞机降落
题目描述 N 架飞机准备降落到某个只有一条跑道的机场。其中第 i 架飞机在 Ti 时刻到达机场上空,到达时它的剩余油料还可以继续盘旋 Di 个单位时间,即它最早 可以于 Ti 时刻开始降落,最晚可以于 Ti Di 时刻开始降落。降落过程需要 Li个单位时…...
STM32 串口实验(学习一)
本章将实现如下功能:STM32通过串口和上位机对话,STM32在收到上位机发过来的字符串后,原原本本返回给上位机。 STM32 串口简介 串口作为MCU的重要外部接口,同时也是软件开发重要的调试手段,其重要性不言而喻。现在基本…...
多臂治疗规则的 Qini 曲线(Stefan Wager)
英文题目: Qini Curves for Multi-Armed Treatment Rules 中文题目:多臂治疗规则的 Qini 曲线 单位:Stefan Wager 论文链接: 代码:GitHub - grf-labs/maq: Treatment rule evaluation via the multi-armed Qini …...
NOSQL之Redis配置及优化
目录 一、关系型数据库 二、非关系型数据库 三、关系型数据库和非关系型数据库区别 1、数据存储方式不同 2、扩展方式不同 3、对事务性的支持不同 四、Redis简介 五、Redis优点 (1)具有极高的数据读写速度 (2)支持丰富的…...
植物一区HR | 植物生理组+转录组:揭示豆科植物响应干旱胁迫机制
PlantArray 植物高通量生理学表型监测系统 是一套以植物生理学为基础的高精度,高通量,自动化表型监测系统,集合实验设置、数据分析、决策工具于一身,能够高通量实时动态监测并进行全天候生理及环境参数采集,是进行植物…...
TCP粘包问题
TCP粘包问题 TCP粘包问题造成TCP粘包的原因发送方原因接收方原因 如何处理TCP粘包发送方接收方应用层 为什么UDP没有粘包问题 TCP粘包问题 TCP粘包就是指发送方发送的若干包数据到达接收方时粘成了一包,从接收缓冲区来看,后一包数据的头紧接着前一包数据…...
QT【day1】
登录框: #include "mainwindow.h"MainWindow::MainWindow(QWidget *parent): QMainWindow(parent) {//窗口设置this->setFixedSize(600,600); //大小this->setWindowTitle("MUMU"); //文本内容this->setWindowOpacity(0.8); //透…...
【Golang】Golang进阶系列教程--为什么 Go 不支持 []T 转换为 []interface
文章目录 前言官方解释内存布局程序运行中的内存布局通用方法 前言 在 Go 中,如果 interface{} 作为函数参数的话,是可以传任意参数的,然后通过类型断言来转换。 举个例子: package mainimport "fmt"func foo(v inter…...
两数相加 II——力扣445
题目描述 法一 栈 本题旨在从后往前加,为了逆序处理所有数位,利用栈,把数字压入栈中,再依次取出相加,注意进位!进位是/10,另外需要注意栈的常用函数,push()、pop()、top()࿰…...
js获取上传视频的封面第一帧
代码如下:粘贴到这个在线编辑器里,可以测试效果。 菜鸟教程在线编辑器 <div><div style"flex: 1;border: 1px solid #999; position:relative;color: #333;background-color:#FFF2B8;"><span style"position: absolute…...
Nginx 高可用负载均衡(三种模式)
一、nginx普通集群负载均衡 1、安装keepalived (1)下载 https://www.keepalived.org/download.html(2)解压 tar -zxvf keepalived-2.0.18.tar.gz(3)使用configure命令配置安装目录与核心配置文件所在位置: ./configure --prefix/usr/local/keepalived --sysconf/e…...
Linux tail命令
在Linux中,tail命令用于查看文件的末尾内容。它可以显示文件的最后几行,默认情况下显示最后10行。 以下是一些常见的使用方式和示例: 显示文件的最后10行: tail filename将会显示名为filename的文件的最后10行内容。 显示文件…...
【屏幕适配发展介绍 Objective-C语言】
一、接下来,我们花一天时间,给大家介绍这个屏幕适配 1.那么,屏幕适配,是什么意思啊 我们说,写程序的时候,我们有时候要做 1)系统适配 2)屏幕适配 1)系统适配:是指的你写的这个代码,在iOS6、iOS7、iOS8,在不同的iOS系统下,是不是运行的效果,一致吧 这个指的是…...
Python爬虫实战:研究MechanicalSoup库相关技术
一、MechanicalSoup 库概述 1.1 库简介 MechanicalSoup 是一个 Python 库,专为自动化交互网站而设计。它结合了 requests 的 HTTP 请求能力和 BeautifulSoup 的 HTML 解析能力,提供了直观的 API,让我们可以像人类用户一样浏览网页、填写表单和提交请求。 1.2 主要功能特点…...
Docker 离线安装指南
参考文章 1、确认操作系统类型及内核版本 Docker依赖于Linux内核的一些特性,不同版本的Docker对内核版本有不同要求。例如,Docker 17.06及之后的版本通常需要Linux内核3.10及以上版本,Docker17.09及更高版本对应Linux内核4.9.x及更高版本。…...
Xshell远程连接Kali(默认 | 私钥)Note版
前言:xshell远程连接,私钥连接和常规默认连接 任务一 开启ssh服务 service ssh status //查看ssh服务状态 service ssh start //开启ssh服务 update-rc.d ssh enable //开启自启动ssh服务 任务二 修改配置文件 vi /etc/ssh/ssh_config //第一…...
Python:操作 Excel 折叠
💖亲爱的技术爱好者们,热烈欢迎来到 Kant2048 的博客!我是 Thomas Kant,很开心能在CSDN上与你们相遇~💖 本博客的精华专栏: 【自动化测试】 【测试经验】 【人工智能】 【Python】 Python 操作 Excel 系列 读取单元格数据按行写入设置行高和列宽自动调整行高和列宽水平…...
Debian系统简介
目录 Debian系统介绍 Debian版本介绍 Debian软件源介绍 软件包管理工具dpkg dpkg核心指令详解 安装软件包 卸载软件包 查询软件包状态 验证软件包完整性 手动处理依赖关系 dpkg vs apt Debian系统介绍 Debian 和 Ubuntu 都是基于 Debian内核 的 Linux 发行版ÿ…...
《从零掌握MIPI CSI-2: 协议精解与FPGA摄像头开发实战》-- CSI-2 协议详细解析 (一)
CSI-2 协议详细解析 (一) 1. CSI-2层定义(CSI-2 Layer Definitions) 分层结构 :CSI-2协议分为6层: 物理层(PHY Layer) : 定义电气特性、时钟机制和传输介质(导线&#…...
visual studio 2022更改主题为深色
visual studio 2022更改主题为深色 点击visual studio 上方的 工具-> 选项 在选项窗口中,选择 环境 -> 常规 ,将其中的颜色主题改成深色 点击确定,更改完成...
【快手拥抱开源】通过快手团队开源的 KwaiCoder-AutoThink-preview 解锁大语言模型的潜力
引言: 在人工智能快速发展的浪潮中,快手Kwaipilot团队推出的 KwaiCoder-AutoThink-preview 具有里程碑意义——这是首个公开的AutoThink大语言模型(LLM)。该模型代表着该领域的重大突破,通过独特方式融合思考与非思考…...
【android bluetooth 框架分析 04】【bt-framework 层详解 1】【BluetoothProperties介绍】
1. BluetoothProperties介绍 libsysprop/srcs/android/sysprop/BluetoothProperties.sysprop BluetoothProperties.sysprop 是 Android AOSP 中的一种 系统属性定义文件(System Property Definition File),用于声明和管理 Bluetooth 模块相…...
安宝特方案丨船舶智造的“AR+AI+作业标准化管理解决方案”(装配)
船舶制造装配管理现状:装配工作依赖人工经验,装配工人凭借长期实践积累的操作技巧完成零部件组装。企业通常制定了装配作业指导书,但在实际执行中,工人对指导书的理解和遵循程度参差不齐。 船舶装配过程中的挑战与需求 挑战 (1…...
