Linux多线程编程实战:深入探索互斥锁的艺术

news2024/12/26 11:10:22
🍑个人主页:Jupiter.
🚀 所属专栏:Linux从入门到进阶
欢迎大家点赞收藏评论😊

在这里插入图片描述

在这里插入图片描述

目录

    • `🦅Linux线程互斥`
      • `🐏进程线程间的互斥相关背景概念`
      • `🦌互斥锁mutex`
            • *下面是一个:操作共享变量会有问题的售票系统代码*
            • *为什么可能无法获得争取结果?*
      • `🐒互斥锁的接口`
            • *改进上面的售票系统:*
      • `🐗RAII风格的加锁解锁`
      • `🐬互斥锁实现原理探究`


🦅Linux线程互斥

🐏进程线程间的互斥相关背景概念

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

🦌互斥锁mutex

大部分情况,线程使用的数据都是局部变量,变量的地址空间在线程栈空间内,这种情况,变量归属单个线程,其他线程无法获得这种变量。
但有时候,很多变量都需要在线程间共享,这样的变量称为共享变量,可以通过数据的共享,完成线程之间的交互。

多个线程并发的操作共享变量,会带来一些问题。

下面是一个:操作共享变量会有问题的售票系统代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>

int ticket = 10;

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;
        }
    }
    return nullptr;
}
int main(void)
{
    pthread_t t1, t2, t3, t4;

    pthread_create(&t1, NULL, route, (void *)"thread 1");
    pthread_create(&t2, NULL, route, (void *)"thread 2");
    pthread_create(&t3, NULL, route, (void *)"thread 3");
    pthread_create(&t4, NULL, route, (void *)"thread 4");

    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    pthread_join(t3, NULL);
    pthread_join(t4, NULL);

    return 0;
}

运行结果:

为什么可能无法获得争取结果?
  • if 语句判断条件为真以后,代码可以并发的切换到其他线程

  • usleep 这个模拟漫长业务的过程,在这个漫长的业务过程中,可能有很多个线程会进入该代码段

  • ticket-- 操作本身就不是一个原子操作

取出ticket- -部分的汇编代码

objdump -d a.out > test.objdump 
152 40064b: 8b 05 e3 04 20 00 mov 0x2004e3(%rip),%eax # 600b34 <ticket> 
153 400651: 83 e8 01 sub $0x1,%eax 
154 400654: 89 05 da 04 20 00 mov %eax,0x2004da(%rip) # 600b34 <ticket>

操作并不是原子操作,而是对应三条汇编指令:

  • load :将共享变量ticket从内存加载到寄存器中
  • update : 更新寄存器里面的值,执行-1操作
  • store :将新值,从寄存器写回共享变量ticket的内存地址

要解决以上问题,需要做到三点:

  • 代码必须要有互斥行为:当代码进入临界区执行时,不允许其他线程进入该临界区。
  • 如果多个线程同时要求执行临界区的代码,并且临界区没有线程在执行,那么只能允许一个线程进入该临界区。
  • 如果线程不在临界区中执行,那么该线程不能阻止其他线程进入临界区。

要做到这三点,本质上就是需要一把锁。Linux上提供的这把锁叫互斥量(互斥锁)
在这里插入图片描述

🐒互斥锁的接口

初始化互斥锁

  • 方法1,静态分配(定义一把静态或则全局的锁):
    pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER
  • 方法2,动态分配(局部的锁):
    int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
    参数:
    • mutex:要初始化的互斥锁
    • attr:NULL

销毁互斥量

  • 销毁互斥量需要注意:
    • 使用 PTHREAD_ MUTEX_ INITIALIZER 初始化的互斥锁不需要销毁
    • 不要销毁一个已经加锁的互斥锁
    • 已经销毁的互斥锁,要确保后面不会有线程再尝试加锁

int pthread_mutex_destroy(pthread_mutex_t *mutex);

互斥量加锁和解锁

int pthread_mutex_lock(pthread_mutex_t *mutex);

  • 申请锁成功,函数返回,允许继续向后运行。
  • 申请锁失败,函数阻塞,不允许向后运行。
  • 函数调用失败,出错返回。

int pthread_mutex_unlock(pthread_mutex_t *mutex);

  • 返回值:成功返回0,失败返回错误号

调用 pthread_ lock 时,可能会遇到以下情况:

  • 互斥锁处于未锁状态,该函数会将互斥锁锁定,同时返回成功
  • 发起函数调用时,其他线程已经锁定互斥锁,或者存在其他线程同时申请互斥锁,但没有竞争到互斥量,那么pthread_ lock调用会陷入阻塞(执行流被挂起),等待互斥锁解锁。
改进上面的售票系统:
int ticket = 100;

// 定义一个全局的锁
pthread_mutex_t mutex = PTHREAD_ADAPTIVE_MUTEX_INITIALIZER_NP;

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;
        }
    }
    return nullptr;
}

int main(void){
    pthread_t t1, t2, t3, t4;

    pthread_create(&t1, NULL, route, (void *)"thread 1");
    pthread_create(&t2, NULL, route, (void *)"thread 2");
    pthread_create(&t3, NULL, route, (void *)"thread 3");
    pthread_create(&t4, NULL, route, (void *)"thread 4");

    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    pthread_join(t3, NULL);
    pthread_join(t4, NULL);

    return 0;
}

运行结果:加锁后没有出现减到负数的情况。
在这里插入图片描述

🐗RAII风格的加锁解锁

#ifndef __LOCK_GUARD_HPP__
#define __LOCK_GUARD_HPP__

#include <pthread.h>
#include <iostream>

class LockGuard
{
public:
    LockGuard(pthread_mutex_t *mutex) : _mutex(mutex)
    {
        pthread_mutex_lock(_mutex);
    }

    ~LockGuard()
    {
        pthread_mutex_unlock(_mutex);
    }

private:
    pthread_mutex_t *_mutex;
};

#endif

🐬互斥锁实现原理探究

经过上面的例子,已经意识到单纯的i++ 或者 ++i不是原子的,有可能会有数据一致性问题为了实现互斥锁操作,大多数体系结构都提供了swap或exchange指令,该指令的作用是把寄存器和内存单元的数据相交换,由于只有一条指令,保证了原子性,即使是多处理器平台,访问内存的总线周期也有先后,一个处理器上的交换指令执行时另一个处理器的交换指令只能等待总线周期。 现在我们把lock和unlock的伪代码改一下,下面mutex的初始值为1,
在这里插入图片描述


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2120216.html

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

相关文章

九、外观模式

外观模式&#xff08;Facade Pattern&#xff09;是一种结构型设计模式&#xff0c;有叫门面模式&#xff0c;它为一个复杂子系统提供一个简单的接口&#xff0c;隐藏系统的复杂性。通过使用外观模式&#xff0c;客户端可以更方便地和复杂的系统进行交互&#xff0c;而无需直接…

在国产芯片上实现YOLOv5/v8图像AI识别-【4.4】RK3588网络摄像头推理后推流到RTSP更多内容见视频

本专栏主要是提供一种国产化图像识别的解决方案&#xff0c;专栏中实现了YOLOv5/v8在国产化芯片上的使用部署&#xff0c;并可以实现网页端实时查看。根据自己的具体需求可以直接产品化部署使用。 B站配套视频&#xff1a;https://www.bilibili.com/video/BV1or421T74f 前言…

机器学习TFIDF的情感分类文章

一.中文分词 当读者使用Python爬取了中文数据集之后&#xff0c;首先需要对数据集进行中文分词处理。由于英文中的词与词之间是采用空格关联的&#xff0c;按照空格可以直接划分词组&#xff0c;所以不需要进行分词处理&#xff0c;而中文汉字之间是紧密相连的&#xff0c;并且…

HTML零基础教程(超详细)

一、什么是HTML HTML&#xff0c;全称超文本标记语言&#xff08;HyperText Markup Language&#xff09;&#xff0c;是一种用于创建网页的标准标记语言。它通过一系列标签来定义网页的结构、内容和格式。HTML文档是由HTML元素构成的文本文件&#xff0c;这些元素包括标题、段…

《Nginx核心技术》第16章:实现Nginx的高可用负载均衡

作者&#xff1a;冰河 星球&#xff1a;http://m6z.cn/6aeFbs 博客&#xff1a;https://binghe.gitcode.host 文章汇总&#xff1a;https://binghe.gitcode.host/md/all/all.html 星球项目地址&#xff1a;https://binghe.gitcode.host/md/zsxq/introduce.html 沉淀&#xff0c…

[数据结构] 开散列法 闭散列法 模拟实现哈希结构(一)

标题&#xff1a;[数据结构] 开散列法 && 闭散列法 模拟实现哈希结构 个人主页&#xff1a;水墨不写bug 目录 一、闭散列法 核心逻辑的解决 i、为什么要设置位置状态&#xff1f;&#xff08;伪删除法的使用&#xff09; ii、哈希函数的设计 接口的实现 1、插入&a…

Linux 常用命令 - tail 【显示文件最后几行内容】

简介 tail 这个命令源自英文单词 “尾巴”&#xff0c;它的主要功能是显示文件的最后几行内容。通过使用 tail&#xff0c;用户可以查看文件的最新添加内容&#xff0c;特别是对于监控日志文件来说非常有用。tail 命令默认显示文件的最后 10 行&#xff0c;但这可以通过参数调…

走进低代码报表开发(一):探秘报表数据源

在前文当中&#xff0c;我们对勤研低代码平台的流程设计功能进行了介绍。接下来&#xff0c;让我们一同深入了解在企业日常运营中另一个极为常见的报表功能。在当今数字化时代&#xff0c;高效的报表生成对于企业的决策至关重要。勤研低代码开发平台能够以卓越的性能和便捷的操…

Git 学习与使用

0 认识⼯作区、暂存区、版本库 ⼯作区&#xff1a;是在电脑上你要写代码或⽂件的⽬录。 暂存区&#xff1a;英⽂叫stage或index。⼀般存放在.git ⽬录下的index⽂件&#xff08;.git/index&#xff09;中&#xff0c;我们 把暂存区有时也叫作索引&#xff08;index&#xff09;…

LAMP环境下项目部署

目录 1、创建一台虚拟机 centos 源的配置 备份源 修改源 重新加载缓存 安装软件 2、关闭防火墙和selinux 查看防火墙状态 关闭防火墙 查看SELinux的状态 临时关闭防火墙 永久关闭SELinux&#xff1a;编辑SELinux的配置文件 配置文件的修改内容 3、检查系统中是否…

NFTScan | 09.02~09.08 NFT 市场热点汇总

欢迎来到由 NFT 基础设施 NFTScan 出品的 NFT 生态热点事件每周汇总。 周期&#xff1a;2024.09.02~ 2024.09.08 NFT Hot News 01/ 数据&#xff1a;NFT 8 月销售额跌破 4 亿美元&#xff0c;创年内新低 9 月 2 日&#xff0c;数据显示&#xff0c;8 月 NFT 的月销售额仅为 …

直播相关01-录制麦克风声音,QT上 .pro 将 linux,mac和windows上配置为三种可以共享, 在.pro文件中 message 的作用

一 QT 上的 .pro 文件 将 linux&#xff0c;mac和windows上配置设置为可以共享 1. 先来看文件夹布局 2. 再来看 QT 中的 .pro文件 .pro 文件的写法 QT core guigreaterThan(QT_MAJOR_VERSION, 4): QT widgetsCONFIG c11# The following define makes your compiler …

【FFMPEG】FFplay音视频同步分析(下)

audio_decode_frame函数分析 首先说明一下&#xff0c;audio_decode_frame() 函数跟解码毫无关系&#xff0c;真正的解码函数是 decoder_decode_frame 。 audio_decode_frame() 函数的主要作用是从 FrameQueue 队列里面读取 AVFrame &#xff0c;然后把 is->audio_buf 指向…

多路转接之poll(接口介绍,struct pollfd介绍,实现原理,实现非阻塞网络通信代码)

目录 poll 引入 介绍 函数原型 fds struct pollfd 特点 nfds timeout 取值 返回值 原理 如何实现关注多个fd? 如何确定哪个fd上有事件就绪? 如何区分事件类型? 判断某事件是否就绪的方法 代码 示例 总结 为什么说它解决了fd上限问题? 缺点 poll 引入…

DVWA通关教程

Brute Force Low 先进行一下代码审计 <?php // 检查是否通过GET请求传递了Login参数&#xff08;注意&#xff1a;这里应该是username或类似的&#xff0c;但代码逻辑有误&#xff09; if( isset( $_GET[ Login ] ) ) { // 从GET请求中获取用户名 $user $_GET[ us…

【学习笔记】手写 Tomcat -- 预备知识

目录 一、新建项目 二、IO流 1. 什么是IO流&#xff1f; 2. IO的流向说明图解 3. IO 流的分类 4. 字节流 输出流 字节输出流的细节 输入流 字节输入流的细节 5. 练习 6. 字符流 输入流 字符流读取的细节 字符输入流原理解析 字符输出流原理解析 三、网络编程 …

NVIDIA GH200 超级芯片:重塑超算性能与AI基准的革新之作

Nvidia 正在将其 GH200 芯片应用于欧洲超级计算机&#xff0c;研究人员正在着手研究这些系统并发布带有性能基准的研究论文。 在第一篇论文《理解紧密耦合异构系统中的数据移动&#xff1a;以 Grace Hopper 超级芯片为例》中&#xff0c;研究人员对 GH200 的各种应用进行了基准…

vue2关闭eslint

vue2关闭eslint 1、找到项目build目录下的webpack.base.conf.js文件 2、注释createLintingRule()里面的内容&#xff08;只注释里面的内容&#xff09; 3、重启项目即可

自己动手实现mybatis的底层框架(不用动态代理直接用执行器、用动态代理自己实现。图文分析!)

目录 一.原生mybits框架图分析 自己实现Mybatis框架的分析 两种框架操作数据库的方法&#xff1a; 二.搭建开发环境 1.先创建一个maven项目 2.加入依赖(mysql dom4j junit lombok) 三.mybatis框架的设计思路 具体实现过程 3.1实现任务阶段 1- 完成读取配置文件&#x…

基于 TiDB 资源管控 + TiCDC 实现多业务融合容灾测试

导读 随着金融行业的不断发展&#xff0c;多个业务系统的整合成为了趋势&#xff0c;分布式数据库的应用也愈发广泛。为了应对多业务融合带来的复杂性&#xff0c;金融机构需要在保障各业务系统高效运行的同时&#xff0c;确保 IT 系统的高可用性和稳定性。本文将介绍 TiDB 如…