【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系统下,是不是运行的效果,一致吧 这个指的是…...
脑机新手指南(八):OpenBCI_GUI:从环境搭建到数据可视化(下)
一、数据处理与分析实战 (一)实时滤波与参数调整 基础滤波操作 60Hz 工频滤波:勾选界面右侧 “60Hz” 复选框,可有效抑制电网干扰(适用于北美地区,欧洲用户可调整为 50Hz)。 平滑处理&…...
Zustand 状态管理库:极简而强大的解决方案
Zustand 是一个轻量级、快速和可扩展的状态管理库,特别适合 React 应用。它以简洁的 API 和高效的性能解决了 Redux 等状态管理方案中的繁琐问题。 核心优势对比 基本使用指南 1. 创建 Store // store.js import create from zustandconst useStore create((set)…...
Debian系统简介
目录 Debian系统介绍 Debian版本介绍 Debian软件源介绍 软件包管理工具dpkg dpkg核心指令详解 安装软件包 卸载软件包 查询软件包状态 验证软件包完整性 手动处理依赖关系 dpkg vs apt Debian系统介绍 Debian 和 Ubuntu 都是基于 Debian内核 的 Linux 发行版ÿ…...
企业如何增强终端安全?
在数字化转型加速的今天,企业的业务运行越来越依赖于终端设备。从员工的笔记本电脑、智能手机,到工厂里的物联网设备、智能传感器,这些终端构成了企业与外部世界连接的 “神经末梢”。然而,随着远程办公的常态化和设备接入的爆炸式…...
毫米波雷达基础理论(3D+4D)
3D、4D毫米波雷达基础知识及厂商选型 PreView : https://mp.weixin.qq.com/s/bQkju4r6med7I3TBGJI_bQ 1. FMCW毫米波雷达基础知识 主要参考博文: 一文入门汽车毫米波雷达基本原理 :https://mp.weixin.qq.com/s/_EN7A5lKcz2Eh8dLnjE19w 毫米波雷达基础…...
链式法则中 复合函数的推导路径 多变量“信息传递路径”
非常好,我们将之前关于偏导数链式法则中不能“约掉”偏导符号的问题,统一使用 二重复合函数: z f ( u ( x , y ) , v ( x , y ) ) \boxed{z f(u(x,y),\ v(x,y))} zf(u(x,y), v(x,y)) 来全面说明。我们会展示其全微分形式(偏导…...
【深尚想】TPS54618CQRTERQ1汽车级同步降压转换器电源芯片全面解析
1. 元器件定义与技术特点 TPS54618CQRTERQ1 是德州仪器(TI)推出的一款 汽车级同步降压转换器(DC-DC开关稳压器),属于高性能电源管理芯片。核心特性包括: 输入电压范围:2.95V–6V,输…...
CppCon 2015 学习:REFLECTION TECHNIQUES IN C++
关于 Reflection(反射) 这个概念,总结一下: Reflection(反射)是什么? 反射是对类型的自我检查能力(Introspection) 可以查看类的成员变量、成员函数等信息。反射允许枚…...
Appium下载安装配置保姆教程(图文详解)
目录 一、Appium软件介绍 1.特点 2.工作原理 3.应用场景 二、环境准备 安装 Node.js 安装 Appium 安装 JDK 安装 Android SDK 安装Python及依赖包 三、安装教程 1.Node.js安装 1.1.下载Node 1.2.安装程序 1.3.配置npm仓储和缓存 1.4. 配置环境 1.5.测试Node.j…...
Redis上篇--知识点总结
Redis上篇–解析 本文大部分知识整理自网上,在正文结束后都会附上参考地址。如果想要深入或者详细学习可以通过文末链接跳转学习。 1. 基本介绍 Redis 是一个开源的、高性能的 内存键值数据库,Redis 的键值对中的 key 就是字符串对象,而 val…...
