【Linux笔记】——线程互斥与互斥锁的封装

news2025/5/20 0:35:12

🔥个人主页🔥:孤寂大仙V
🌈收录专栏🌈:Linux
🌹往期回顾🌹:【Linux笔记】——Linux线程封装
🔖流水不争,争的是滔滔不息


  • 一、线程互斥的概念
  • 二、互斥量
  • 三、互斥量接口
    • 初始化互斥量
    • 销毁互斥量
    • 互斥量加锁与解锁
  • 四、对互斥量底层进行理解
  • 五、互斥锁的封装

一、线程互斥的概念

线程互斥是一种用于多线程编程的技术,旨在确保同一时间只有一个线程可以访问共享资源。在多线程环境中,多个线程可能会同时访问和修改共享数据,导致数据不一致或竞态条件。通过使用互斥机制,可以避免这些问题,确保线程安全。

线程互斥说白了,就是让线程串行访问资源,避免线程并行访问资源使数据出现错乱。比如一个资源被一个线程访问并执行得到的结果已经是正确的结果了,这时候又来了一个线程,这个线程又访问并执行一遍这个资源,数据就会出现错乱。

这里注意不要混淆,线程互斥是目的,互斥量是工具,锁是通用属于。

二、互斥量

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

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>

int ticket = 1000;

void *route(void *arg)
{
    char *id = (char *)arg;
    while (1)
    {
        if (ticket > 0) // 1. 判断
        {
            usleep(1000);                               // 模拟抢票花的时间
            printf("%s sells ticket:%d\n", id, ticket); // 2. 抢到了票
            ticket--;                                   // 3. 票数--
        }
        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);
}

这个代码在不加锁的情况下非常容易出问题。原因就在于 ticket > 0 的判断和 ticket-- 之间不是原子操作,也就是说多个线程可能会同时判断通过,然后都执行了 ticket–,导致数据错乱。
if语句判断条件为真以后,代码可以并发的切换到其他线程。usleep(1000); 模拟抢票业务的过程,在这个过程中可能有很多线程线程进入该代码。这就不是原子的。

解决上述问题,一、代码要有互斥行为,当代码进入临界区执行的时候,其他线程不允许进入该临界区。二、如果多个线程同时要求执行临界区的代码,并且临界区没有线程在执行,那么只允许一个线程进入该临界区。三、如果线程不在临界区执行,那么该线程不能阻止其他线程进入临界区。为了做到上面这三点,本质上需要一把锁,Linux把这把锁叫做互斥量。

什么是原子性
原子操作是指在多线程或多进程环境中,某个操作一旦开始就不会被中断,直到操作完成。这种操作要么完全执行,要么完全不执行,不会出现部分执行的情况。原子操作常用于确保数据的一致性,特别是在并发编程中。
原子操作具有不可分割性,即在执行过程中不会被其他线程或进程打断。这种特性使得原子操作非常适合用于实现锁、信号量等同步机制。
原子操作广泛应用于并发编程中,特别是在需要确保数据一致性的场景。例如,在多线程环境下,多个线程可能会同时访问和修改共享变量,此时使用原子操作可以避免竞态条件(Race
Condition)的发生。

三、互斥量接口

初始化互斥量

静态分配

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER

这个是全局的,不需要释放,程序结束自动释放
动态分配

int pthread_mutex_init(pthread_mutex_t *restrict mutex, const
pthread_mutexattr_t *restrict attr);

参数:mutex是要初始化的互斥量,attr是NULL。

销毁互斥量

int pthread_mutex_destroy(pthread_mutex_t *mutex)
  • 使用 PTHREAD_ MUTEX_ INITIALIZER 初始化的互斥量不需要销毁
  • 不要销毁⼀个已经加锁的互斥量
  • 已经销毁的互斥量,要确保后面不会有线程再尝试加锁

互斥量加锁与解锁

加锁

int n=pthread_mutex_lock(&_mutex);

解锁

int n=pthread_mutex_unlock(&_mutex);

互斥量处于未锁状态,该函数会将互斥量锁定,同时返回成功。

发起函数调时,其他线程已经锁定互斥量,或者存在其他线程同时申请互斥量,但没有竞争到互斥量,那么pthread_ lock调用会陷入阻塞(执行流被挂起),等待互斥量解锁。


在这里插入图片描述

在这里插入图片描述
对临界区进行保护,本质就是用锁对临界区的代码进行保护。

四、对互斥量底层进行理解

为了实现互斥操作,大多数体系结构都提供了swap和exchange指令,该指令的作用是把寄存器和内存单元的数据相互交换。

CPU的寄存器硬件只有一套,但是CPU寄存器内的数据可以有多份,这些多份的数据就是线程或者进程的上下文数据。把一个变量的内容交到CPU寄存器的内部,就是把该变量的内容,获取到当前执行流的硬件上下文中。我们用swap和exchange将内存与寄存器数据进行交换,就是获取锁的过程就是把当前线程的上下文数据进行交换,所以谁申请到谁持有锁。简单理解的话是,上下文数据是每个线程/进程所特有的不共享的,把硬件理解为一个容器放着锁的地方,且这个容器是不回变的,变的是里面线程/进程的上下文数据,所以进来的线程/进程的上下文数据申请到锁交换走了就持有了锁。CPU 是一个“硬件容器”,每次执行线程就像“把自己的上下文装进容器”。而锁的本质是某段共享数据的访问控制权。谁抢到了这份共享数据的使用权(通常是一个“锁变量”成功从 0 → 1 的原子修改),谁就持有锁。

实际上,锁的竞争与上下文本身没有直接交换关系。锁操作是通过某些汇编指令实现的原子操作(如 lock xchg),是线程在“当前占有 CPU 执行权”时尝试修改锁变量。

在这里插入图片描述
在这里插入图片描述
当有一个线程a,在寄存器清零(注意我们上面的聊的独立的上下文数据,这里清零只是线程a上下文在寄存器这个“容器”清零了),然后与内存单元的数据1就是锁,进行交换。这时候线程a持有了锁。那么这时候把线程a切走挂起,线程b进来清零然后与内存单元交换也不会持有锁。这里也从侧面说明了原子性。当释放锁的时候,只需要内存单元mutex数据置1就可以了。上述是形象化理解。
在这里插入图片描述
原子锁操作的关键在于:线程通过原子指令在“自己的上下文”中进行“对内存中锁变量”的原子修改,谁成功交换就谁持有锁。锁的状态独立于线程调度、寄存器内容,完全由共享内存中的那一份锁变量决定。

正统理解:
在这里插入图片描述

五、互斥锁的封装

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

using namespace std;

namespace MutexModule
{
    class Mutex
    {
    public:
        Mutex()
        {
            pthread_mutex_init(&_mutex, nullptr); // 初始化互斥锁
        }

        void Lock()
        {
            int n = pthread_mutex_lock(&_mutex); // 上锁
            if (n == 0)
            {
                // cout<<"上锁成功"<<endl;
            }
        }

        void Unlock()
        {
            int n = pthread_mutex_unlock(&_mutex); // 解锁
            if (n == 0)
            {
                // cout<<"解锁成功"<<endl;
            }
        }
        ~Mutex()
        {
            pthread_mutex_destroy(&_mutex); // 销毁互斥锁
        }
        pthread_mutex_t *Get()//外部调用拿到
        {
            return &_mutex;
        }
    private:
        pthread_mutex_t _mutex; // 定义互斥锁
    };

    class LockGuard // RAII自动上锁解锁
    {
    public:
        LockGuard(Mutex &mutex)
            : _mutex(mutex)
        {
            _mutex.Lock();
        }

        ~LockGuard()
        {
            _mutex.Unlock();
        }

    private:
        Mutex &_mutex;
    };

    
}

初始化锁,销毁锁,上锁,解锁,函数往上招呼就行了。加个自动上锁,直接调这个LockGuard在临界区前调用,不用手动写释放了。

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

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

相关文章

Android屏幕采集编码打包推送RTMP技术详解:从开发到优化与应用

在现代移动应用中&#xff0c;屏幕采集已成为一个广泛使用的功能&#xff0c;尤其是在实时直播、视频会议、远程教育、游戏录制等场景中&#xff0c;屏幕采集技术的需求不断增长。Android 平台为开发者提供了 MediaProjection API&#xff0c;这使得屏幕录制和采集变得更加简单…

【深度学习】残差网络(ResNet)

如果按照李沐老师书上来&#xff0c;学完 VGG 后还有 NiN 和 GoogLeNet 要学&#xff0c;但是这两个我之前听都没听过&#xff0c;而且我看到我导师有发过 ResNet 相关的论文&#xff0c;就想跳过它们直接看后面的内容。 现在看来这不算是不踏实&#xff0c;因为李沐老师说如果…

《Python星球日记》 第94天:走近自动化训练平台

名人说&#xff1a;路漫漫其修远兮&#xff0c;吾将上下而求索。—— 屈原《离骚》 创作者&#xff1a;Code_流苏(CSDN)&#xff08;一个喜欢古诗词和编程的Coder&#x1f60a;&#xff09; 目录 一、自动化训练平台简介1. Kubeflow Pipelines2. TensorFlow Extended (TFX) 二…

S7 200 smart连接Profinet转ModbusTCP网关与西门子1200PLC配置案例

控制要求&#xff1a;使用MODBUSTCP通信进行两台PLC之间的数据交换&#xff0c;由于改造现场不能改动程序&#xff0c;只留出了对应的IQ地址。于是客户决定使用网关进行通讯把数据传到plc。 1、读取服务器端40001~40005地址中的数据&#xff0c;放入到VW200~VW208中&#xff1…

学习笔记:黑马程序员JavaWeb开发教程(2025.4.7)

12.9 登录校验-Filter-入门 /*代表所有&#xff0c;WebFilter&#xff08;urlPatterns “/*”&#xff09;代表拦截所有请求 Filter是JavaWeb三大组件&#xff0c;不是SpringBoot提供的&#xff0c;要在SpringBoot里面使用JavaWeb&#xff0c;则需要加上ServletComponentScan注…

OpenCV-python灰度变化和直方图修正类型

实验1 实验内容 该段代码旨在读取名为"test.png"的图像&#xff0c;并将其转换为灰度图像。使用加权平均值法将原始图像的RGB值转换为灰度值。 代码注释 image cv.imread("test.png")h np.shape(image)[0] w np.shape(image)[1] gray_img np.zeros…

图像定制大一统?字节提出DreamO,支持人物生成、 ID保持、虚拟试穿、风格迁移等多项任务,有效解决多泛化性冲突。

字节提出了一个统一的图像定制框架DreamO&#xff0c;支持人物生成、 ID保持、虚拟试穿、风格迁移等多项任务&#xff0c;不仅在广泛的图像定制场景中取得了高质量的结果&#xff0c;而且在适应多条件场景方面也表现出很强的灵活性。现在已经可以支持消费级 GPU&#xff08;16G…

Nginx 动静分离在 ZKmall 开源商城静态资源管理中的深度优化

在 B2C 电商高并发场景下&#xff0c;静态资源&#xff08;图片、CSS、JavaScript 等&#xff09;的高效管理直接影响页面加载速度与用户体验。ZKmall开源商城通过对 Nginx 动静分离技术的深度优化&#xff0c;将静态资源响应速度提升 65%&#xff0c;带宽成本降低 40%&#xf…

在vs code 中无法运行npm并报无法将“npm”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。请检查

问题&#xff1a; npm : 无法将“npm”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。请检查 原因&#xff1a; 可能是环境变量未正确继承或终端配置不一致 解决方法&#xff1a; 1.找到自己的node.js的版本号 2.重新下载node.js 下载 node.js - https://nodejs.p…

分布式2(限流算法、分布式一致性算法、Zookeeper )

目录 限流算法 固定窗口计数器&#xff08;Fixed Window Counter&#xff09; 滑动窗口计数器&#xff08;Sliding Window Counter&#xff09; 漏桶算法&#xff08;Leaky Bucket&#xff09; 令牌桶算法&#xff08;Token Bucket&#xff09; 令牌桶与漏桶的对比 分布式…

ARM A64 LDR指令

ARM A64 LDR指令 1 LDR (immediate)1.1 Post-index1.2 Pre-index1.3 Unsigned offset 2 LDR (literal)3 LDR (register)4 其他LDR指令变体4.1 LDRB (immediate)4.1.1 Post-index4.1.2 Pre-index4.1.3 Unsigned offset 4.2 LDRB (register)4.3 LDRH (immediate)4.3.1 Post-index…

实习记录小程序|基于SSM+Vue的实习记录小程序设计与实现(源码+数据库+文档)

实习记录小程序 目录 基于SSM的习记录小程序设计与实现 一、前言 二、系统设计 三、系统功能设计 1、小程序端&#xff1a; 2、后台 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取&#xff1a; 博主介绍&#xff1a;✌️大厂码…

老旧设备升级利器:Modbus TCP转 Profinet让能效监控更智能

在工业自动化领域&#xff0c;ModbusTCP和Profinet是两种常见的通讯协议。Profinet是西门子公司推出的基于以太网的实时工业以太网标准&#xff0c;而Modbus则是由施耐德电气提出的全球首个真正开放的、应用于电子控制器上的现场总线协议。这两种协议各有各的优点&#xff0c;但…

【从基础到模型网络】深度学习-语义分割-ROI

在语义分割中&#xff0c;ROI&#xff08;Region of Interest&#xff0c;感兴趣区域&#xff09;是图像中需要重点关注的部分。其作用包括&#xff1a;提高效率&#xff0c;减少高分辨率图像的计算量&#xff1b;增强分割精度&#xff0c;聚焦关键语义信息&#xff1b;减少背景…

掌握Docker:从运行到挂载的全面指南

目录 1. Docker的运行2. 查看Docker的启动日志3. 停止容器4. 容器的启动5. 删除容器6. 查看容器的详细信息7.一条命令关闭所有容器拓展容器的复制&#xff08;修改数据不会同步&#xff09;容器的挂载&#xff08;修改数据可以同步&#xff09;挂载到现有容器 1. Docker的运行 …

Pandas pyecharts数据可视化基础③

pyecharts基础绘图案例解析 引言思维导图代码案例分析 提前安装依赖同样操作安装完重新启动Jupyter Notebook三维散点图&#xff08;代码5 - 40&#xff09; 代码结果代码解析 漏斗图&#xff08;代码5 - 41&#xff09;结果代码解析 词云图&#xff08;代码5 - 42&#xff09;…

数据库中关于查询选课问题的解法

前言 今天上午起来复习了老师上课讲的选课问题。我总结了三个解法以及一点注意事项。 选课问题介绍 简单来说就是查询某某同学没有选或者选了什么课。然后查询出该同学的姓名&#xff0c;学号&#xff0c;课程号&#xff0c;课程名之类的。 sql文件我上传了。大家可以尝试练…

基于Bootstrap 的网页html css 登录页制作成品

目录 前言 一、网页制作概述 二、登录页面 2.1 HTML内容 2.2 CSS样式 三、技术说明书 四、页面效果图 前言 ‌Bootstrap‌是一个用于快速开发Web应用程序和网站的前端框架&#xff0c;由Twitter的设计师Mark Otto和Jacob Thornton合作开发。 它基于HTML、CSS和JavaScri…

组件导航 (Navigation)+flutter项目搭建-混合开发+分栏

组件导航 (Navigation)flutter项目搭建 接上一章flutter项目的环境变量配置并运行flutter 上一章面熟了搭建flutter并用编辑器运行了ohos项目&#xff0c;这章主要是对项目的工程化改造 先创建flutter项目&#xff0c;再配置Navigation 1.在开发视图的resources/base/profi…

ProfibusDP主站转modbusTCP网关与ABB电机保护器数据交互

ProfibusDP主站转modbusTCP网关与ABB电机保护器数据交互 在工业自动化领域&#xff0c;Profibus DP&#xff08;Process Field Bus&#xff09;和Modbus TCP是两种常见的通讯协议&#xff0c;它们各自在不同的场合发挥着重要作用。然而&#xff0c;随着技术的发展和应用需求的…