当前位置: 首页 > news >正文

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

666

线程的互斥与同步

  • 💫 概念引入
    • ⭐️临界资源(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函数等待线程结束,确保所有线程都执行完毕后程序才结束。

💥运行结果

001

🔥分析

其实代码是存在Bug的
我们试想一下,== tickets-- == 这段代码执行需要几步?
在上一篇博客中有介绍到,线程的运行可能会产生临时数据放在内存,这里的tickets就是如此,我们知道执行–操作是要经过CPU的,那么 执行该代码最少需要3步:

  1. 把tickets拷贝到CPU
  2. CPU执行–操作
  3. 把–后的值放回给对象!
    而在此多线程执行抢票的过程中,随时可能进行线程的切换(由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;
}
  • 运行
    0002
    这时候就解决了~

🌈 注意事项

  1. 临界区,只要对临界区加锁,而且加锁的粒度约细越好
  2. 加锁的本质是让线程执行临界区代码串行化
  3. 加锁是一套规范,通过临界区对临界资源进行访问的时候,要加就都要加
  4. 锁保护的是临界区, 任何线程执行临界区代码访问临界资源,都必须先申请锁,前提是都必须先看到锁!
  5. 这把锁,本身不就也是临界资源吗?锁的设计者早就想到了 pthread_mutex_lock: 竞争和申请锁的过程,就是原子的!

Q:为什么在if和else后面都要 执行: pthread_mutex_unlock(&mutex);来释放 直接放在循环的最后会有什么问题?
A:会造成其他在锁外的线程无线在阻塞等待 … 不解锁 不结束!

0000

☀️ 🌤 ⛅️ 🌥 ☁️ 🌦 🌧 ⛈ 🌩 🌨 ❄️ ☃️ ⛄️ 🌬 💨 💧 💦 ☔️ ☂️ 🌊 🌫

相关文章:

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

线程的互斥与同步 &#x1f4ab; 概念引入⭐️临界资源&#xff08;Critical Resource&#xff09;&#xff1a;&#x1f31f;临界区&#xff08;Critical Section&#xff09;&#xff1a;✨互斥&#xff08;Mutex&#xff09;&#xff1a; ⚡️结合代码看互斥☄️ 代码逻辑&a…...

ajax概述

目录 1.什么是ajax 2.ja原生ajax 3.jQuery框架的ajax 4.综合案例 1.什么是ajax Ajax 即"Asynchronous Javascript And XML"&#xff08;异步 JavaScript 和 XML&#xff09;&#xff0c;是指一种创建交互式网页应用的网页开发技术。Ajax 异步 JavaScript 和 XML&…...

小白带你学习linux的mysql服务(主从mysql服务和读写分离三十一)

目录 二、MySQL Replication优点&#xff1a; 三、MySQL复制类型 1、异步复制&#xff08;Asynchronous repication&#xff09; 2、全同步复制&#xff08;Fully synchronous replication&#xff09; 3、半同步复制&#xff08;Semisynchronous replication&#xff09;…...

【低代码专题方案】iPaaS运维方案,助力企业集成平台智能化高效运维

01 场景背景 随着IT行业的发展和各家企业IT建设的需要&#xff0c;信息系统移动化、社交化、大数据、系统互联、数据打通等需求不断增多&#xff0c;企业集成平台占据各个企业领域&#xff0c;成为各业务系统数据传输的中枢。 集成平台承接的业务系统越多&#xff0c;集成平台…...

Android SDK 上手指南||第一章 环境需求||第二章 IDE:Eclipse速览

第一章 环境需求 这是我们系列教程的第一篇&#xff0c;让我们来安装Android的开发环境并且把Android SDK运行起来&#xff01; 介绍 欢迎来到Android SDK入门指南系列文章&#xff0c;如果你想开始开发Android App&#xff0c;这个系列将从头开始教你所须的技能。我们假定你…...

Amazon Linux上使用ec2-user来设置开机自启动的shell脚本

要在Amazon Linux上使用ec2-user来设置开机自启动的shell脚本&#xff0c;可以按照以下步骤操作&#xff1a; 1. 确保您拥有要设置自启动的shell脚本。假设脚本的路径是/home/ec2-user/myscript.sh。 2. 使用以下命令打开/etc/rc.d/rc.local文件&#xff1a; shell sudo nano /…...

【Spring】Spring 下载及其 jar 包

根据 【动力节点】最新Spring框架教程&#xff0c;全网首套Spring6教程&#xff0c;跟老杜从零学spring入门到高级 以及老杜的原版笔记 https://www.yuque.com/docs/share/866abad4-7106-45e7-afcd-245a733b073f?# 《Spring6》 进行整理&#xff0c; 文档密码&#xff1a;mg9b…...

蓝桥杯2023年第十四届省赛-飞机降落

题目描述 N 架飞机准备降落到某个只有一条跑道的机场。其中第 i 架飞机在 Ti 时刻到达机场上空&#xff0c;到达时它的剩余油料还可以继续盘旋 Di 个单位时间&#xff0c;即它最早 可以于 Ti 时刻开始降落&#xff0c;最晚可以于 Ti Di 时刻开始降落。降落过程需要 Li个单位时…...

STM32 串口实验(学习一)

本章将实现如下功能&#xff1a;STM32通过串口和上位机对话&#xff0c;STM32在收到上位机发过来的字符串后&#xff0c;原原本本返回给上位机。 STM32 串口简介 串口作为MCU的重要外部接口&#xff0c;同时也是软件开发重要的调试手段&#xff0c;其重要性不言而喻。现在基本…...

多臂治疗规则的 Qini 曲线(Stefan Wager)

英文题目&#xff1a; Qini Curves for Multi-Armed Treatment Rules 中文题目&#xff1a;多臂治疗规则的 Qini 曲线 单位&#xff1a;Stefan Wager 论文链接&#xff1a; 代码&#xff1a;GitHub - grf-labs/maq: Treatment rule evaluation via the multi-armed Qini …...

NOSQL之Redis配置及优化

目录 一、关系型数据库 二、非关系型数据库 三、关系型数据库和非关系型数据库区别 1、数据存储方式不同 2、扩展方式不同 3、对事务性的支持不同 四、Redis简介 五、Redis优点 &#xff08;1&#xff09;具有极高的数据读写速度 &#xff08;2&#xff09;支持丰富的…...

植物一区HR | 植物生理组+转录组:揭示豆科植物响应干旱胁迫机制

PlantArray 植物高通量生理学表型监测系统 是一套以植物生理学为基础的高精度&#xff0c;高通量&#xff0c;自动化表型监测系统&#xff0c;集合实验设置、数据分析、决策工具于一身&#xff0c;能够高通量实时动态监测并进行全天候生理及环境参数采集&#xff0c;是进行植物…...

TCP粘包问题

TCP粘包问题 TCP粘包问题造成TCP粘包的原因发送方原因接收方原因 如何处理TCP粘包发送方接收方应用层 为什么UDP没有粘包问题 TCP粘包问题 TCP粘包就是指发送方发送的若干包数据到达接收方时粘成了一包&#xff0c;从接收缓冲区来看&#xff0c;后一包数据的头紧接着前一包数据…...

QT【day1】

登录框&#xff1a; #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 中&#xff0c;如果 interface{} 作为函数参数的话&#xff0c;是可以传任意参数的&#xff0c;然后通过类型断言来转换。 举个例子&#xff1a; package mainimport "fmt"func foo(v inter…...

两数相加 II——力扣445

题目描述 法一 栈 本题旨在从后往前加&#xff0c;为了逆序处理所有数位&#xff0c;利用栈&#xff0c;把数字压入栈中&#xff0c;再依次取出相加&#xff0c;注意进位&#xff01;进位是/10&#xff0c;另外需要注意栈的常用函数&#xff0c;push()、pop()、top()&#xff0…...

js获取上传视频的封面第一帧

代码如下&#xff1a;粘贴到这个在线编辑器里&#xff0c;可以测试效果。 菜鸟教程在线编辑器 <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命令配置安装目录与核心配置文件所在位置&#xff1a; ./configure --prefix/usr/local/keepalived --sysconf/e…...

Linux tail命令

在Linux中&#xff0c;tail命令用于查看文件的末尾内容。它可以显示文件的最后几行&#xff0c;默认情况下显示最后10行。 以下是一些常见的使用方式和示例&#xff1a; 显示文件的最后10行&#xff1a; tail filename将会显示名为filename的文件的最后10行内容。 显示文件…...

【屏幕适配发展介绍 Objective-C语言】

一、接下来,我们花一天时间,给大家介绍这个屏幕适配 1.那么,屏幕适配,是什么意思啊 我们说,写程序的时候,我们有时候要做 1)系统适配 2)屏幕适配 1)系统适配:是指的你写的这个代码,在iOS6、iOS7、iOS8,在不同的iOS系统下,是不是运行的效果,一致吧 这个指的是…...

手游刚开服就被攻击怎么办?如何防御DDoS?

开服初期是手游最脆弱的阶段&#xff0c;极易成为DDoS攻击的目标。一旦遭遇攻击&#xff0c;可能导致服务器瘫痪、玩家流失&#xff0c;甚至造成巨大经济损失。本文为开发者提供一套简洁有效的应急与防御方案&#xff0c;帮助快速应对并构建长期防护体系。 一、遭遇攻击的紧急应…...

ES6从入门到精通:前言

ES6简介 ES6&#xff08;ECMAScript 2015&#xff09;是JavaScript语言的重大更新&#xff0c;引入了许多新特性&#xff0c;包括语法糖、新数据类型、模块化支持等&#xff0c;显著提升了开发效率和代码可维护性。 核心知识点概览 变量声明 let 和 const 取代 var&#xf…...

《Playwright:微软的自动化测试工具详解》

Playwright 简介:声明内容来自网络&#xff0c;将内容拼接整理出来的文档 Playwright 是微软开发的自动化测试工具&#xff0c;支持 Chrome、Firefox、Safari 等主流浏览器&#xff0c;提供多语言 API&#xff08;Python、JavaScript、Java、.NET&#xff09;。它的特点包括&a…...

2.Vue编写一个app

1.src中重要的组成 1.1main.ts // 引入createApp用于创建应用 import { createApp } from "vue"; // 引用App根组件 import App from ./App.vue;createApp(App).mount(#app)1.2 App.vue 其中要写三种标签 <template> <!--html--> </template>…...

Rust 异步编程

Rust 异步编程 引言 Rust 是一种系统编程语言,以其高性能、安全性以及零成本抽象而著称。在多核处理器成为主流的今天,异步编程成为了一种提高应用性能、优化资源利用的有效手段。本文将深入探讨 Rust 异步编程的核心概念、常用库以及最佳实践。 异步编程基础 什么是异步…...

Python如何给视频添加音频和字幕

在Python中&#xff0c;给视频添加音频和字幕可以使用电影文件处理库MoviePy和字幕处理库Subtitles。下面将详细介绍如何使用这些库来实现视频的音频和字幕添加&#xff0c;包括必要的代码示例和详细解释。 环境准备 在开始之前&#xff0c;需要安装以下Python库&#xff1a;…...

自然语言处理——Transformer

自然语言处理——Transformer 自注意力机制多头注意力机制Transformer 虽然循环神经网络可以对具有序列特性的数据非常有效&#xff0c;它能挖掘数据中的时序信息以及语义信息&#xff0c;但是它有一个很大的缺陷——很难并行化。 我们可以考虑用CNN来替代RNN&#xff0c;但是…...

C++使用 new 来创建动态数组

问题&#xff1a; 不能使用变量定义数组大小 原因&#xff1a; 这是因为数组在内存中是连续存储的&#xff0c;编译器需要在编译阶段就确定数组的大小&#xff0c;以便正确地分配内存空间。如果允许使用变量来定义数组的大小&#xff0c;那么编译器就无法在编译时确定数组的大…...

从 GreenPlum 到镜舟数据库:杭银消费金融湖仓一体转型实践

作者&#xff1a;吴岐诗&#xff0c;杭银消费金融大数据应用开发工程师 本文整理自杭银消费金融大数据应用开发工程师在StarRocks Summit Asia 2024的分享 引言&#xff1a;融合数据湖与数仓的创新之路 在数字金融时代&#xff0c;数据已成为金融机构的核心竞争力。杭银消费金…...

上位机开发过程中的设计模式体会(1):工厂方法模式、单例模式和生成器模式

简介 在我的 QT/C 开发工作中&#xff0c;合理运用设计模式极大地提高了代码的可维护性和可扩展性。本文将分享我在实际项目中应用的三种创造型模式&#xff1a;工厂方法模式、单例模式和生成器模式。 1. 工厂模式 (Factory Pattern) 应用场景 在我的 QT 项目中曾经有一个需…...