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

Linux_线程互斥

互斥的相关概念

  • 共享资源:指多个进程或线程可以共同访问和操作的资源
  • 临界资源:被保护的共享资源就叫做临界资源
  • 临界区:每个线程内部,访问临界资源的代码,就叫做临界区
  • 互斥:任何时刻,互斥保证有且只有一个执行流进⼊临界区,访问临界资源,通常对临界资源起保护作用
  • 原子性:不会被任何调度机制打断的操作,该操作只有两态,要么完成,要么未完成。

一个不加保护的Demo

这里使用多个线程共同执行上面的方法,代码很简单。但是运行结果怎么出现了负数?等于0不就直接break了吗?

原因有以下两点

  • 代码if(XXXX)不是原子操作,ticketnum--也不是原子操作
  • 所有的线程在尽可能多的进行调度切换执行 --- 线程或者进程什么时候会切换?
    • a.时间片耗尽
    • b.更高优先级的进程要调度
    • c.通过sleep,从内核返回用户时,会进行时间片是否到达的检测,进而导致切换

当我们执行上述代码时,每个线程都要这样执行上面的逻辑,但cpu的寄存器只有一套,但是寄存器中的数据有多套,且数据为线程私有。由于ticketnum--操作不是原子的(即,将ticketnum的值移动到CPU,CPU做运算,再将结果写回内存。共三步)。当一个线程正走到以上逻辑的第二步时,正准备判断,此时这个线程被切换了,一旦被切换,当前线程在寄存器中数据都会保存下来,等在被切回来的时候,再恢复!

        当票数为1时,a线程会做判断,符合逻辑进入if,走到usleep语句;此时b线程也进来了,a将寄存器中的数据带走,此时b线程见到的票数也是1,b线程也符合逻辑,进入if,也会走到usleep;同样的c和d线程都会做以上线程的动作,都会进入if。当a过了usleep时间,会执行--操作(1.重读数据2.--数据3.写回数据),此时票数为0了,同样的b,c,d线程也会做--,因为它们已经进入了if中。最后就导致票数为-2的情况了。

互斥量mutex

在Linux中互斥量就是锁

要解决上述多线程并发引起的安全问题,我们只需在进入临界区之前加上一把锁,就可以完美解决。

 互斥量(锁)的相关接口

  • pthread_mutex_init: 初始化互斥锁。
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;// 使用宏值初始化(全局)

    mutex:指向要初始化的互斥锁对象的指针。

    attr:指定互斥锁属性的对象,如果传递NULL,则使用默认的互斥锁属性。

    pthread_mutex_init 函数若调用成功,会返回 0。若发生错误,会返回一个非零的错误码。

    • pthread_mutex_destroy: 销毁互斥锁。
    int pthread_mutex_destroy(pthread_mutex_t *mutex);
    
    • pthread_mutex_lock: 锁定互斥锁
    int pthread_mutex_lock(pthread_mutex_t *mutex);
    
    • pthread_mutex_unlock: 解锁互斥锁。
     int pthread_mutex_unlock(pthread_mutex_t *mutex);
    

    锁接口的使用

    全局锁

    // 定义一个全局锁
    pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
    int ticketnum = 10000; // 共享资源,临界资源void Ticket()
    {while (true){pthread_mutex_lock(&lock); // 加锁if (ticketnum > 0){usleep(1000);printf("get a new ticket, id: %d\n", ticketnum--);pthread_mutex_unlock(&lock);     // 解锁}else{pthread_mutex_unlock(&lock);     // 解锁break;}}
    }
    int main()
    {// 创建多线程的逻辑,调用Tichetreturn 0;
    }

     局部锁

    使用ThreadData接收参数,包括锁的接收,保证每一个线程都能看到同一把锁

    #include <iostream>
    #include <string>
    #include <pthread.h>
    #include <functional>
    #include <sys/types.h>
    #include <unistd.h>namespace ThreadModule
    {// 要传递的参数struct ThreadData{ThreadData(const std::string &name, pthread_mutex_t *lock_ptr): _name(name), _lock_ptr(lock_ptr){}std::string _name;pthread_mutex_t *_lock_ptr;};// 执行任务的方法using func_t = std::function<void(ThreadData*)>;// 线程状态-枚举enum class TSTATUS{NEW,RUNNING,STOP};class Thread{private:// 成员方法,具备this指针,置为static之后就不具备this指针了static void *Routine(void *args){// t就拿到了this指针Thread *t = static_cast<Thread *>(args);t->_status = TSTATUS::RUNNING;t->_func(t->_td); // 就可以执行相应的类内方法了return nullptr;}public:// 线程要执行的方法直接传进来Thread(const std::string &name, func_t func, ThreadData* td): _name(name), _func(func), _td(td), _status(TSTATUS::NEW), _joinable(true){}bool Start(){if (_status != TSTATUS::RUNNING){int n = ::pthread_create(&_tid, nullptr, Routine, this); // 将this指针通过参数传过去if (n != 0)return false;return true;}return false;}bool Stop(){if (_status == TSTATUS::RUNNING){int n = ::pthread_cancel(_tid);if (n != 0)return false;_status = TSTATUS::STOP;return true;}return false;}bool Join(){if (_joinable){int n = ::pthread_join(_tid, nullptr);if (n != 0)return false;_status = TSTATUS::STOP;return true;}return false;}std::string Name() { return _name; }~Thread() {}private:std::string _name; // 线程名字pthread_t _tid;    // 线程idbool _joinable;    // 是否是分离状态,默认不是func_t _func;      // 线程未来要执行的方法TSTATUS _status;   // 线程状态ThreadData* _td;   // 要传递的参数};
    }
    

    让每个线程都获取局部锁的地址,在每个线程在执行抢票逻辑的时候,将锁的地址传给加锁函数,就能实现局部加锁了。 

    #include "Thread.hpp"
    #include <vector>int ticketnum = 10000;
    void Ticket(ThreadModule::ThreadData *td)
    {while(true){pthread_mutex_lock(td->_lock_ptr);      // 加锁if(ticketnum > 0){// 抢票printf("get a new ticket, who get it: %s, id: %d\n", td->_name.c_str(), ticketnum--);pthread_mutex_unlock(td->_lock_ptr);// 解锁}else{pthread_mutex_unlock(td->_lock_ptr);// 解锁break;}}
    }
    #define NUM 4
    int main()
    {// 创建局部锁pthread_mutex_t mutex;pthread_mutex_init(&mutex, nullptr);// 创建线程对象std::vector<ThreadModule::Thread> threads;for(int i = 0;i < NUM; i++){std::string name = "thread-" + std::to_string(i+1);// 把锁的地址给到td对象ThreadModule::ThreadData *td = new ThreadModule::ThreadData(name, &mutex);// 之后在将td给到Threadthreads.emplace_back(name, Ticket, td);}// 启动线程for(int i = 0; i< NUM;i++)threads[i].Start();// 等待线程for(int i = 0; i< NUM;i++)threads[i].Join();// 释放锁pthread_mutex_destroy(&mutex);  return 0;
    }

    锁的相关问题

    1. 锁本身是全局的,那么锁也是共享资源!谁保证锁的安全?

            pthread_mutex:加锁和解锁被设计成为原子的了

    2. 如何看待锁呢?二元信号量就是锁!

            2.1 加锁本质就是对资源展开预订!

            2.2 整体使用资源!!

    3. 如果申请锁的时候,锁被别人已经拿走了,怎么办?

            其他线程要进行阻塞等待

    4. 线程在访问临界区代码的时候,可以不可以切换?可以切换!!

            4.1 我被切走的时候,别人能进来吗?不能!因为我是抱着锁,被切换的!临界区的代码就是被串行的!这也是加锁效率低的原因!也体现了原子性(要么不做,要么做完)!

    锁是如何实现的

    现在大家已经意识到单纯的 i++ 或者 ++i 都不是原子的,有可能会有数据一致性问题。
    在内核中,为了实现互斥锁操作,大多数体系结构都提供了swap或exchange指令,该指令的作用是把寄存器和内存单元的数据相交换,由于只有一条指令,保证了原子性,即使是多处理器平台,访问内存的 总线周期也有先后,一个处理器上的交换指令执行时,另一个处理器的交换指令只能等待总线周期。 

    将%al看成一个寄存器,把 0 movb到 %al中,xchgb将内存中的变量与寄存器中的做了直接的交换,不需要中间变量。(我们假定mutex一开始的数据是:1表示锁没有被申请;0表示锁被申请了)。线程执行判断,如果%al中的内容>0,则申请锁成功然后返回,否则挂起等待,等待完成被唤醒,goto lock重新申请锁。

    1. CPU的寄存器只有一套,被所有的线程共享。但是寄存器中的数据,属于执行流上下文,属于执行流私有的数据!
    2. CUP在执行代码的时候,一定要有对应的执行载体 -- 线程&&进程。
    3. 数据在内存中,是被所有线程所共享的

    结论:把数据从内存移动到寄存器,本质是把数据从共享,变成线程私有!


    重新理解加锁

    当线程A执行第一行代码时,此时%al寄存器中为0,内存mutex中为1(图1);执行第二条代码时内存中mutex中的数据与%al进行交换,变为%al中值为1,mutex的值为0(图2);我们假设线程A执行第三行代码的时候被切换走,线程A会保存自身的上下文,带走%al中的数据,此时线程A处在第三行。

            这时线程B来了,并且开始走第一行和第二行代码,由于内存中mutex的值为0(还是处于图2的状态),交换之后%al的值还是0。所以当线程B执行到第3行代码的时候只能跳到第6行,进行挂起等待。

            线程B被挂起,线程A被重新切回,并恢复上文数据,从第三行开始执行,进入if,调用接口pthread_mutex_lock,return 0表示加锁成功,进入临界区。所以此时线程A被称为:申请锁成功。在上面代码中,加锁就是执行第二行代码:xchgb,只有一条汇编代码,交换不是拷贝,只有一个“1”,持有1的,就表示持有锁!

            当线程A执行完临界区的代码后,进行解锁,执行第八行代码,将自身持有的“1”movb到内存中(这样就回到了图1的状态),接着唤醒正在等待mutex的线程B,线程B被唤醒后,执行第七行代码,继续goto lock重新申请锁。

    相关文章:

    Linux_线程互斥

    互斥的相关概念 共享资源&#xff1a;指多个进程或线程可以共同访问和操作的资源临界资源&#xff1a;被保护的共享资源就叫做临界资源临界区&#xff1a;每个线程内部&#xff0c;访问临界资源的代码&#xff0c;就叫做临界区互斥&#xff1a;任何时刻&#xff0c;互斥保证有…...

    基于 NodeJs 一个后端接口的创建过程及其规范 -- 【elpis全栈项目】

    基于 NodeJs 一个后端接口的创建过程及其规范 一个接口的诞生&#xff1a; #mermaid-svg-46HXZKI3fdnO0rKV {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-46HXZKI3fdnO0rKV .error-icon{fill:#552222;}#mermaid-sv…...

    企业知识库提升企业核心竞争力促进团队协作和知识分享

    内容概要 在快速发展的数字化时代&#xff0c;企业知识库的构建与运用变得愈发重要。其重要性不仅体现在信息的集中管理上&#xff0c;更在于推动企业整体竞争力的提升。一个高效的知识库可以作为团队合作的重要平台&#xff0c;促进不同部门之间的信息交流与协作&#xff0c;…...

    C++ unordered_map和unordered_set的使用,哈希表的实现

    文章目录 unordered_map&#xff0c;unorder_set和map &#xff0c;set的差异哈希表的实现概念直接定址法哈希冲突哈希冲突举个例子 负载因子将关键字转为整数哈希函数除法散列法/除留余数法 哈希冲突的解决方法开放定址法线性探测二次探测 开放定址法代码实现 哈希表的代码 un…...

    games101-作业3

    由于此次试验需要加载模型&#xff0c;涉及到本地环节&#xff0c;如果是windows系统&#xff0c;需要对main函数中的路径稍作改变&#xff1a; 这么写需要&#xff1a; #include "windows.h" 该段代码&#xff1a; #include "windows.h" int main(int ar…...

    【Block总结】高效多尺度注意力EMA,超越SE、CBAM、SA、CA等注意力|即插即用

    论文信息 标题: Efficient Multi-Scale Attention Module with Cross-Spatial Learning 作者: Daliang Ouyang, Su He, Guozhong Zhang, Mingzhu Luo, Huaiyong Guo, Jian Zhan, Zhijie Huang 论文链接: https://arxiv.org/pdf/2305.13563v2 GitHub链接: https://github.co…...

    Pwn 入门核心工具和命令大全

    一、调试工具&#xff08;GDB 及其插件&#xff09; GDB 启动调试&#xff1a;gdb ./binary 运行程序&#xff1a;run 或 r 设置断点&#xff1a;break *0x地址 或 b 函数名 查看寄存器&#xff1a;info registers 查看内存&#xff1a;x/10wx 0x地址 &#xff08;查看 10 个 …...

    探索AI(chatgpt、文心一言、kimi等)提示词的奥秘

    大家好&#xff0c;我是老六哥&#xff0c;我正在共享使用AI提高工作效率的技巧。欢迎关注我&#xff0c;共同提高使用AI的技能&#xff0c;让AI成功你的个人助理。 "AI提示词究竟是什么&#xff1f;" 这是许多初学者在接触AI时的共同疑问。 "我阅读了大量关于…...

    利用飞书机器人进行 - ArXiv自动化检索推荐

    相关作者的Github仓库 ArXivToday-Lark 使用教程 Step1 新建机器人 根据飞书官方机器人使用手册&#xff0c;新建自定义机器人&#xff0c;并记录好webhook地址&#xff0c;后续将在配置文件中更新该地址。 可以先完成到后续步骤之前&#xff0c;后续的步骤与安全相关&…...

    小白爬虫冒险之反“反爬”:无限debugger、禁用开发者工具、干扰控制台...(持续更新)

    背景浅谈 小白踏足JS逆向领域也有一年了&#xff0c;对于逆向这个需求呢主要要求就是让我们去破解**“反爬机制”**&#xff0c;即反“反爬”&#xff0c;脚本处理层面一般都是decipher网站对request设置的cipher&#xff0c;比如破解一个DES/AES加密拿到key。这篇文章先不去谈…...

    Ubuntu中MySQL安装-02

    服务器端安装 安装服务器端&#xff1a;在终端中输入如下命令&#xff0c;回车后&#xff0c;然后按照提示输入 sudo apt-get install mysql-server 当前使用的ubuntu镜像中已经安装好了mysql服务器端&#xff0c;无需再安装&#xff0c;并且设置成了开机自启动服务器用于接…...

    大数据相关职位介绍之一(数据分析,数据开发,数据产品经理,数据运营)

    大数据相关职位介绍之一 随着大数据、人工智能&#xff08;AI&#xff09;和机器学习的快速发展&#xff0c;数据分析与管理已经成为各行各业的重要组成部分。从互联网公司到传统行业的数字转型&#xff0c;数据相关职位在中国日益成为推动企业创新和提升竞争力的关键力量。以…...

    使用DeepSeek API生成Markdown文件

    DeepSeek技术应用与代码实现 一、DeepSeek简介 DeepSeek是一款强大的人工智能写作助手&#xff0c;能够根据用户输入的提示&#xff08;Prompt&#xff09;快速生成高质量的文章。它不仅支持批量生成文章&#xff0c;还能通过智能分段、Markdown转HTML等功能优化内容。此外&…...

    java多线程学习笔记

    文章目录 关键词1.什么是多线程以及使用场景?2.并发与并行3.多线程实现3.1继承 Thread 类实现3.2Runnable 接口方式实现3.3Callable接口/Future接口实现3.4三种方式总结 4.常见的成员方法&#xff08;重点记忆&#xff09;94.1setName/currentThread/sleep要点4.2线程的优先级…...

    Manticore Search,新一代搜索引擎之王

    吊打ES&#xff0c;新一代搜索引擎之王 概述 Manticore Search 是一个开源的分布式搜索引擎&#xff0c;专注于高性能和低延迟的搜索场景。 它基于 Sphinx 搜索引擎开发&#xff0c;继承了 Sphinx 的高效索引和查询能力&#xff0c;并在分布式架构、实时搜索、易用性等方面进…...

    【MySQL】数据类型与表约束

    目录 数据类型分类 数值类型 tinyint类型 bit类型 小数类型 字符串类型 日期和时间类型 enum和set 表的约束 空属性 默认值 列描述 zerofill 主键 自增长 唯一键 外键 数据类型分类 数值类型 tinyint类型 MySQL中&#xff0c;整形可以是有符号和无符号的&…...

    CAG技术:提升LLM响应速度与质量

    标题&#xff1a;CAG技术&#xff1a;提升LLM响应速度与质量 文章信息摘要&#xff1a; CAG&#xff08;Cache-Augmented Generation&#xff09;通过预加载相关知识到LLM的扩展上下文中&#xff0c;显著减少了检索延迟和错误&#xff0c;从而提升了响应速度和质量。与传统的R…...

    上位机知识篇---Linux源码编译安装链接命令

    文章目录 前言第一部分&#xff1a;Linux源码编译安装1. 安装编译工具2. 下载源代码3. 解压源代码4. 配置5. 编译6. 测试&#xff08;可选&#xff09;7. 安装8. 清理&#xff08;可选&#xff09;9.注意事项 第二部分&#xff1a;链接命令硬链接&#xff08;Hard Link&#xf…...

    科研绘图系列:R语言绘制线性回归连线图(line chart)

    禁止商业或二改转载,仅供自学使用,侵权必究,如需截取部分内容请后台联系作者! 文章目录 介绍加载R包数据下载导入数据数据预处理画图保存图片系统信息参考介绍 科研绘图系列:R语言绘制线性回归连线图(line chart) 加载R包 library(tidyverse) library(ggthemes) libra…...

    将ollama迁移到其他盘(eg:F盘)

    文章目录 1.迁移ollama的安装目录2.修改环境变量3.验证 背景&#xff1a;在windows操作系统中进行操作 相关阅读 &#xff1a;本地部署deepseek模型步骤 1.迁移ollama的安装目录 因为ollama默认安装在C盘&#xff0c;所以只能安装好之后再进行手动迁移位置。 # 1.迁移Ollama可…...

    大数据零基础学习day1之环境准备和大数据初步理解

    学习大数据会使用到多台Linux服务器。 一、环境准备 1、VMware 基于VMware构建Linux虚拟机 是大数据从业者或者IT从业者的必备技能之一也是成本低廉的方案 所以VMware虚拟机方案是必须要学习的。 &#xff08;1&#xff09;设置网关 打开VMware虚拟机&#xff0c;点击编辑…...

    2024年赣州旅游投资集团社会招聘笔试真

    2024年赣州旅游投资集团社会招聘笔试真 题 ( 满 分 1 0 0 分 时 间 1 2 0 分 钟 ) 一、单选题(每题只有一个正确答案,答错、不答或多答均不得分) 1.纪要的特点不包括()。 A.概括重点 B.指导传达 C. 客观纪实 D.有言必录 【答案】: D 2.1864年,()预言了电磁波的存在,并指出…...

    OkHttp 中实现断点续传 demo

    在 OkHttp 中实现断点续传主要通过以下步骤完成&#xff0c;核心是利用 HTTP 协议的 Range 请求头指定下载范围&#xff1a; 实现原理 Range 请求头&#xff1a;向服务器请求文件的特定字节范围&#xff08;如 Range: bytes1024-&#xff09; 本地文件记录&#xff1a;保存已…...

    从零开始打造 OpenSTLinux 6.6 Yocto 系统(基于STM32CubeMX)(九)

    设备树移植 和uboot设备树修改的内容同步到kernel将设备树stm32mp157d-stm32mp157daa1-mx.dts复制到内核源码目录下 源码修改及编译 修改arch/arm/boot/dts/st/Makefile&#xff0c;新增设备树编译 stm32mp157f-ev1-m4-examples.dtb \stm32mp157d-stm32mp157daa1-mx.dtb修改…...

    OpenLayers 分屏对比(地图联动)

    注&#xff1a;当前使用的是 ol 5.3.0 版本&#xff0c;天地图使用的key请到天地图官网申请&#xff0c;并替换为自己的key 地图分屏对比在WebGIS开发中是很常见的功能&#xff0c;和卷帘图层不一样的是&#xff0c;分屏对比是在各个地图中添加相同或者不同的图层进行对比查看。…...

    AI病理诊断七剑下天山,医疗未来触手可及

    一、病理诊断困局&#xff1a;刀尖上的医学艺术 1.1 金标准背后的隐痛 病理诊断被誉为"诊断的诊断"&#xff0c;医生需通过显微镜观察组织切片&#xff0c;在细胞迷宫中捕捉癌变信号。某省病理质控报告显示&#xff0c;基层医院误诊率达12%-15%&#xff0c;专家会诊…...

    智能AI电话机器人系统的识别能力现状与发展水平

    一、引言 随着人工智能技术的飞速发展&#xff0c;AI电话机器人系统已经从简单的自动应答工具演变为具备复杂交互能力的智能助手。这类系统结合了语音识别、自然语言处理、情感计算和机器学习等多项前沿技术&#xff0c;在客户服务、营销推广、信息查询等领域发挥着越来越重要…...

    人工智能(大型语言模型 LLMs)对不同学科的影响以及由此产生的新学习方式

    今天是关于AI如何在教学中增强学生的学习体验&#xff0c;我把重要信息标红了。人文学科的价值被低估了 ⬇️ 转型与必要性 人工智能正在深刻地改变教育&#xff0c;这并非炒作&#xff0c;而是已经发生的巨大变革。教育机构和教育者不能忽视它&#xff0c;试图简单地禁止学生使…...

    MFC 抛体运动模拟:常见问题解决与界面美化

    在 MFC 中开发抛体运动模拟程序时,我们常遇到 轨迹残留、无效刷新、视觉单调、物理逻辑瑕疵 等问题。本文将针对这些痛点,详细解析原因并提供解决方案,同时兼顾界面美化,让模拟效果更专业、更高效。 问题一:历史轨迹与小球残影残留 现象 小球运动后,历史位置的 “残影”…...

    Kubernetes 网络模型深度解析:Pod IP 与 Service 的负载均衡机制,Service到底是什么?

    Pod IP 的本质与特性 Pod IP 的定位 纯端点地址&#xff1a;Pod IP 是分配给 Pod 网络命名空间的真实 IP 地址&#xff08;如 10.244.1.2&#xff09;无特殊名称&#xff1a;在 Kubernetes 中&#xff0c;它通常被称为 “Pod IP” 或 “容器 IP”生命周期&#xff1a;与 Pod …...