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

【Linux】从互斥原理到C++ RAII封装实践

📢博客主页:https://blog.csdn.net/2301_779549673
📢欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正!
📢本文由 JohnKi 原创,首发于 CSDN🙉
📢未来很长,值得我们全力奔赴更美好的生活✨

在这里插入图片描述

在这里插入图片描述

文章目录

  • 📢前言
  • 🏳️‍🌈一、从场景看互斥:为什么需要锁?
  • 🏳️‍🌈二、互斥锁核心概念图解
    • 2.1 临界区与非临界区
    • 2. 2 进程线程间的互斥相关背景概念
  • 🏳️‍🌈三、Linux互斥锁原理剖析
    • 3.2 初始化互斥量
    • 3.3 销毁互斥量
    • 3.3 pthread_mutex 底层实现
  • 🏳️‍🌈四、C++ RAII封装实战
    • 4.1 基础互斥类(Mutex)
    • 4.2 守卫锁(LockGuard)
  • 🏳️‍🌈五、完整代码
    • 5.1 Mutex.hpp
    • 5.2 Mutex.cc
    • 5.3 Makefile
  • 👥总结


📢前言

紧接上回的线程C++封装,这回笔者着重介绍一下互斥的原理和其必要性,并手把手使用C++封装一个RAII模型。

还有一点,笔者之后的封装都会使用之前博客中封装好的容器,需要的可以去仓库或者前面的博客中自取。

RAII 的核心思想是将资源的获取和初始化放在对象的构造函数中进行,而资源的释放放在对象的析构函数中进行。当对象被创建时,其构造函数会自动执行,从而完成资源的获取;当对象的生命周期结束时,其析构函数会被自动调用,从而完成资源的释放。这样,资源的生命周期就与对象的生命周期绑定在一起,利用 C++ 等语言的对象自动销毁机制来确保资源的正确释放。


🏳️‍🌈一、从场景看互斥:为什么需要锁?

假设你的银行账户余额是1000元,同时有两个线程执行转账操作:

  • ​线程A:存入200元 → balance += 200
  • ​线程B:取出300元 → balance -= 300
    无锁情况下可能的执行顺序:
线程A读取balance(1000) → 线程B读取balance(1000) → 
线程A写入1200 → 线程B写入700  
最终结果:700元(正确应为900元)

🏳️‍🌈二、互斥锁核心概念图解

2.1 临界区与非临界区

在这里插入图片描述

void* thread_func(void* arg) {// 非临界区(可并发执行)prepare_data();  // 临界区(需互斥访问)pthread_mutex_lock(&mtx);update_shared_resource();  pthread_mutex_unlock(&mtx);// 非临界区(可并发执行)post_process();
}

关键特征:

🔵 ​非临界区:允许多线程并行(如图中绿色区域)
🔴 ​临界区:同一时刻仅一个线程执行(红色区域)

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

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

🏳️‍🌈三、Linux互斥锁原理剖析

核心操作流程:

sequenceDiagramparticipant 线程Aparticipant 互斥锁participant 内核线程A->>互斥锁: pthread_mutex_lock()alt 锁空闲互斥锁-->>线程A: 立即获得锁else 锁被占线程A->>内核: 进入休眠队列内核-->>线程A: 唤醒并获取锁end线程A->>临界区: 执行操作线程A->>互斥锁: pthread_mutex_unlock()

3.2 初始化互斥量

方法一:静态分布

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

3.3 销毁互斥量

销毁互斥量需要注意:

  • 使用 PTHREAD_MUTEXINITIALIZER 初始化的互斥量不需要销毁
  • 不要销毁一个已经加锁的互斥量
  • 已经销毁的互斥量,要确保后面不会有线程再尝试加锁
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_mutex_lock 时,可能会遇到以下情况:

  • 互斥量处于未锁状态,该函数会将互斥量锁定,同时返回成功
  • 发起函数调用时,其他线程已经锁定互斥量,或者存在其他线程同时申请互斥量,但没有竞争到互斥量,那么pthread lock调用会陷入阻塞(执行流被挂起),等待互斥量解锁。

3.3 pthread_mutex 底层实现

// 锁结构(简化为x86实现)
struct pthread_mutex {int __lock;          // 锁状态标识int __count;         // 递归锁计数器int __owner;         // 持有者线程IDint __kind;          // 锁类型标识// ... 其他字段
};

🏳️‍🌈四、C++ RAII封装实战

4.1 基础互斥类(Mutex)

// 互斥锁封装类(不可拷贝构造/赋值)class Mutex{public:// 禁止拷贝(保护系统锁资源)Mutex(const Mutex&) = delete;const Mutex& operator = (const Mutex&) = delete;// 构造函数:初始化POSIX互斥锁Mutex(){// 初始化互斥锁属性为默认值int n = ::pthread_mutex_init(&_lock, nullptr);(void)n; // 实际开发建议处理错误码}// 析构函数:销毁锁资源~Mutex(){// 确保锁已处于未锁定状态int n = ::pthread_mutex_destroy(&_lock);(void)n; // 生产环境应检查返回值}// 加锁操作(阻塞直至获取锁)void Lock(){// 可能返回EDEADLK(死锁检测)等错误码int n = ::pthread_mutex_lock(&_lock);(void)n; // 简化处理,实际建议抛异常或记录日志}// 解锁操作(必须由锁持有者调用)void Unlock(){// 未持有锁时解锁将返回EPERMint n = ::pthread_mutex_unlock(&_lock);(void)n; }private:pthread_mutex_t _lock; // 底层锁对象};

4.2 守卫锁(LockGuard)

守卫锁不是新的锁类型,而是对已有锁的自动化生命周期管理工具。这种设计模式完美契合图示中"Lock-unlock"边界需要严格匹配的核心诉求

守卫锁工作流程

sequenceDiagramparticipant 线程participant 守卫锁participant 互斥锁线程->>守卫锁: 创建LockGuard对象守卫锁->>互斥锁: 调用Lock()互斥锁-->>守卫锁: 获得锁线程->>临界区: 执行操作线程->>守卫锁: 对象离开作用域守卫锁->>互斥锁: 调用Unlock()互斥锁-->>其他线程: 释放锁资源

实现

// RAII锁守卫(自动管理锁生命周期)class LockGuard{public:// 构造时加锁(必须传入已初始化的Mutex引用)LockGuard(Mutex &mtx):_mtx(mtx){_mtx.Lock(); // 进入临界区}// 析构时自动解锁(异常安全保证)~LockGuard(){_mtx.Unlock(); // 离开作用域自动释放}private:Mutex &_mtx; // 引用方式持有,避免拷贝导致未定义行为};

🏳️‍🌈五、完整代码

5.1 Mutex.hpp

#pragma once
#include <iostream>
#include <pthread.h> // POSIX线程库头文件namespace LockModule
{// 互斥锁封装类(不可拷贝构造/赋值)class Mutex{public:// 禁止拷贝(保护系统锁资源)Mutex(const Mutex&) = delete;const Mutex& operator = (const Mutex&) = delete;// 构造函数:初始化POSIX互斥锁Mutex(){// 初始化互斥锁属性为默认值int n = ::pthread_mutex_init(&_lock, nullptr);(void)n; // 实际开发建议处理错误码}// 析构函数:销毁锁资源~Mutex(){// 确保锁已处于未锁定状态int n = ::pthread_mutex_destroy(&_lock);(void)n; // 生产环境应检查返回值}// 加锁操作(阻塞直至获取锁)void Lock(){// 可能返回EDEADLK(死锁检测)等错误码int n = ::pthread_mutex_lock(&_lock);(void)n; // 简化处理,实际建议抛异常或记录日志}// 解锁操作(必须由锁持有者调用)void Unlock(){// 未持有锁时解锁将返回EPERMint n = ::pthread_mutex_unlock(&_lock);(void)n; }private:pthread_mutex_t _lock; // 底层锁对象};// RAII锁守卫(自动管理锁生命周期)class LockGuard{public:// 构造时加锁(必须传入已初始化的Mutex引用)LockGuard(Mutex &mtx):_mtx(mtx){_mtx.Lock(); // 进入临界区}// 析构时自动解锁(异常安全保证)~LockGuard(){_mtx.Unlock(); // 离开作用域自动释放}private:Mutex &_mtx; // 引用方式持有,避免拷贝导致未定义行为};
}

5.2 Mutex.cc

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <sched.h>int ticket = 0;
pthread_mutex_t mutex;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;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{printf("%s wait on cond!\n", id);pthread_cond_wait(&cond, &mutex); //醒来的时候,会重新申请锁!!printf("%s 被叫醒了\n", id);}pthread_mutex_unlock(&mutex);}return nullptr;
}int main(void)
{pthread_t t1, t2, t3, t4;pthread_mutex_init(&mutex, NULL);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");int cnt = 10;while(true){sleep(5);ticket += cnt;printf("主线程放票喽, ticket: %d\n", ticket);pthread_cond_signal(&cond);}pthread_join(t1, NULL);pthread_join(t2, NULL);pthread_join(t3, NULL);pthread_join(t4, NULL);pthread_mutex_destroy(&mutex);
}

5.3 Makefile

bin=testMutex
cc=g++
src=$(wildcard *.cc)
obj=$(src:.cc=.o)$(bin):$(obj)$(cc) -o $@ $^ -lpthread
%.o:%.cc$(cc) -c $< -std=c++17.PHONY:clean
clean:rm -f $(bin) $(obj).PHONY:test
test:echo $(src)echo $(obj)

👥总结

本篇博文对 从互斥原理到C++ RAII封装实践 做了一个较为详细的介绍,不知道对你有没有帮助呢

觉得博主写得还不错的三连支持下吧!会继续努力的~

请添加图片描述

相关文章:

【Linux】从互斥原理到C++ RAII封装实践

&#x1f4e2;博客主页&#xff1a;https://blog.csdn.net/2301_779549673 &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01; &#x1f4e2;本文由 JohnKi 原创&#xff0c;首发于 CSDN&#x1f649; &#x1f4e2;未来很长&#…...

爬虫案例十三js逆向模拟登录中大网校

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、网站分析二、代码 前言 提示&#xff1a;这里可以添加本文要记录的大概内容&#xff1a; js 逆向模拟登录中大网校 提示&#xff1a;以下是本篇文章正文内…...

WPF窗口读取、显示、修改、另存excel文件——CAD c#二次开发

效果如下&#xff1a; using System.Data; using System.IO; using System.Windows; using Microsoft.Win32; using ExcelDataReader; using System.Text; using ClosedXML.Excel;namespace IfoxDemo {public partial class SimpleWindow : Window{public SimpleWindow(){Initi…...

01 | Go 项目开发极速入门课介绍

提示&#xff1a; 所有体系课见专栏&#xff1a;Go 项目开发极速入门实战课。 你好&#xff0c;欢迎学习本课程。本课程是一个 Go 项目开发极速入门课程。旨在帮助刚学习完 Go 基础语法的 Go 开发者&#xff0c;快速掌握如何开发一个功能相对全面的 Go 项目。 根据课程设计目标…...

Spring Cloud LoadBalancer 原理与实践

背景 当前我们的微服务架构基于Spring Cloud Alibaba体系&#xff0c;通过定制NacosRule实现了跨集群访问和灰度发布功能。但随着Spring Cloud与Nacos版本升级&#xff0c;官方已弃用Ribbon转向LoadBalancer&#xff0c;这要求我们完成以下技术升级&#xff1a; 负载均衡机制…...

Vmware下的openEuler

1.下载openEuler操作系统镜像 https://repo.openeuler.org/openEuler-20.03-LTS/ISO/ 2.在VM新建虚拟机 3.虚拟机联网 我是出现了没有网络&#xff0c;ping不通的问题 参考&#xff1a;https://blog.csdn.net/FHY26828/article/details/140941234 修改文件&#xff1a; 在…...

spring security学习入门指引

学习 Spring Security 可以从以下几个方面逐步深入&#xff0c;结合理论与实践&#xff0c;以下是具体的学习路径建议&#xff1a; 1. 基础准备 • 熟悉 Spring 框架&#xff1a; 先掌握 Spring Core、Spring MVC 和 Spring Boot 的基础&#xff0c;理解依赖注入&#xff08;D…...

【瞎折腾/Dify】使用docker离线部署Dify

文章目录 说在前面安装Docker(外网)获取Dify源码(外网)拉取docker镜像(外网)导出镜像(内网)导入镜像(内网)运行问题 说在前面 外网操作系统&#xff1a;windows内网操作系统&#xff1a;ubuntu外网docker desktop版本&#xff1a;4.29.0外网docker版本&#xff1a;version 26.0…...

Java EE Web环境安装

Java EE Web环境安装 一、JDK安装与测试&#xff08;Windows环境&#xff09; 1. 安装JDK 官网下载&#xff1a;Oracle JDK&#xff08;选择Windows x64 Installer&#xff09;双击安装包&#xff0c;按向导完成安装 ​ 2. 环境变量配置 右键【此电脑】→【属性】→【高级…...

Node.js中HTTPS模块应用详解

1. HTTPS 模块的概念 HTTPS&#xff08;Hypertext Transfer Protocol Secure&#xff09;是 HTTP 的安全版本&#xff0c;通过 SSL/TLS 协议对数据进行加密&#xff0c;确保数据在传输过程中不被窃取或篡改。在 Node.js 中&#xff0c;https 模块提供了创建 HTTPS 服务器和客户…...

我测试了AI搜索:试图替代谷歌搜索

随着人工智能工具的不断进步,探讨它们是否能够取代传统搜索引擎如谷歌,已成为一个引人入胜的话题。 有一点是确定的:并非所有的人工智能都可用。它们需要的主要特性是能够访问互联网以获取最新信息,这就排除了Anthropic的Claude。 人工智能在某些方面可以增强或改进谷歌搜…...

大语言模型基础之‘显存优化‘

上一篇可扩展的训练技术(二)中&#xff0c;我们介绍了零冗余优化器&#xff08;Zero Redundancy Optimizer, Zero&#xff09;&#xff0c;该技术由DeepSpeed代码库提出&#xff0c;主要用于解决数据并行中的模型冗余技术&#xff0c;即在数据并行训练中&#xff0c;每个GPU上都…...

【Nexus】Maven 私服搭建以及上传自己的Jar包

Nexus 安装 docker run -d -uroot --name nexus3 --restartalways -p 8081:8081 -v /data/nexus-data/blobs:/nexus-data/blobs -v /etc/localtime:/etc/localtime sonatype/nexus3这里也提供一下docker-composer的方法 .env 文件 VERSIONlatest CONTAINER_NAMECONTAINER_N…...

css模拟雷达扫描动画

<div class"radar-scan"><div class"radar-container" /></div> 样式&#xff1a; .radar-scan {background-image: linear-gradient(0deg,transparent 24%,rgba(32, 255, 77, 0.15) 25%,rgba(32, 255, 77, 0.15) 26%,transparent 27%,…...

冠珠瓷砖×郭培“惟质致美”品质主题片上映,讲述高定艺术背后的致美品质故事

168年前&#xff0c;一位英国服装设计师&#xff0c;开创了「高级定制」的先河。时至今日&#xff0c;从服装到各行各业「高级定制」始终代表着对完美的极致追求&#xff0c;成为了行业至高境界的象征。 被誉为“中国高定第一人”&#xff0c;高级定制服装设计师郭培&#xff0…...

【Java 基础(人话版)】进制转换

进制的简单介绍 整数可以使用四种不同的进制表示方式&#xff1a; 二进制 (Binary)&#xff1a;由 0 和 1 组成&#xff0c;满 2 进 1&#xff0c;以 0b 或 0B 开头表示。十进制 (Decimal)&#xff1a;由 0-9 组成&#xff0c;满 10 进 1&#xff0c;是最常用的数值表示方式。…...

3DS模拟器使用(pc+安卓)+金手指+存档互传

1、引言 3ds模拟器已经能够在手机端近乎完美模拟了&#xff0c;那么多的3ds游戏&#xff0c;比玩手机游戏舒服多了。 本人是精灵宝可梦的老玩家&#xff0c;从第一世代就一直在玩&#xff0c;刚耍完NDS的第五世代&#xff0c;黑白系列。现在到宝可梦XY了&#xff0c;需要在3d…...

SpaceClaim二次开发(4)

目录 第五章 Storing Custom Data &#xff08;存储自定义数据&#xff09; 5.1 文档属性 5.2 自定义属性 5.3 属性传播 第六章 Identifying Objects in ACIS and Parasolid Files&#xff08;识别ACIS和Parasolid文件中的对象&#xff09; 6.1 导出期间的标识符 6.2 导入和…...

Leetcode3340:检查平衡字符串

题目描述&#xff1a; 给你一个仅由数字 0 - 9 组成的字符串 num。如果偶数下标处的数字之和等于奇数下标处的数字之和&#xff0c;则认为该数字字符串是一个 平衡字符串。 如果 num 是一个 平衡字符串&#xff0c;则返回 true&#xff1b;否则&#xff0c;返回 false。 代码…...

Java多线程基石—内存模型

Java Memory Model Java内存模型&#xff08;JMM&#xff09;&#xff0c;定义了线程如何与内存交互及线程间的可见性、有序性和原子性。 JMM屏蔽了各种硬件和操作系统的访问差异&#xff0c;保证Java程序在各种平台下对内存的访问都能保证一致效果。 1 JMM 可见性 一个线程…...

ArcGIS助力水文分析:数据处理、地图制作与流域特征提取

在水文水环境保护中&#xff0c;对于信息的采集、处理和分析是关键步骤。水文水环境及其相关数据均具有空间分布特征&#xff0c;传统的方法难以发挥作用。地理信息系统&#xff08;GIS&#xff09;强大的空间数据管理和分析功能&#xff0c;在空间信息处理上有独到的优势&…...

从以太网 II 到 VLAN 和 Jumbo Frame:数据帧格式解读

以太网数据帧是计算机网络通信的基本单位&#xff0c;在不同的应用场景中&#xff0c;它的格式有所不同。根据协议标准和用途&#xff0c;以太网数据帧主要包括以太网 II 帧、IEEE 802.3 帧、IEEE 802.1Q VLAN 帧等七种主要类型。为了更好地理解以太网的通信机制&#xff0c;我…...

X86 RouterOS 7.18 设置笔记六:端口映射(IPv4、IPv6)及回流问题

X86 j4125 4网口小主机折腾笔记五&#xff1a;PVE安装ROS RouterOS X86 RouterOS 7.18 设置笔记一&#xff1a;基础设置 X86 RouterOS 7.18 设置笔记二&#xff1a;网络基础设置(IPV4) X86 RouterOS 7.18 设置笔记三&#xff1a;防火墙设置(IPV4) X86 RouterOS 7.18 设置笔记四…...

将 IPoIB 驱动修改为仅使用 RC 模式

摘要 本文档详细介绍了将 Linux 内核中的 IPoIB(IP over InfiniBand)驱动修改为仅使用 RC(Reliable Connection,可靠连接)模式,并移除所有与 TCP/IP 和以太网相关部分的方法。通过这些修改,可以优化 IPoIB 驱动以适应特定的高性能计算场景,提高数据传输的可靠性和效率…...

热修复框架Tinker与Robust原理剖析

热修复框架Tinker与Robust原理剖析 一、热修复技术概述 1.1 什么是热修复 热修复&#xff08;Hot Fix&#xff09;是Android平台上的一种动态修复机制&#xff0c;它允许应用在不重新发布版本的情况下&#xff0c;动态修复线上bug。这种技术对于快速修复线上问题、降低用户流…...

69.Harmonyos NEXT图片预览组件应用实践(二):电商、内容与办公场景

温馨提示&#xff1a;本篇博客的详细代码已发布到 git : https://gitcode.com/nutpi/HarmonyosNext 可以下载运行哦&#xff01; Harmonyos NEXT图片预览组件应用实践&#xff08;二&#xff09;&#xff1a;电商、内容与办公场景 文章目录 Harmonyos NEXT图片预览组件应用实践…...

C题库-判断水仙花数

【数据判断】 问题1&#xff1a;判断水仙花数&#xff0c;水仙花数是指一个三位数&#xff0c;其各位数字的立方和等于该数本身。 方法一&#xff1a; #include<stdio.h>int main(void){int num,Bit,Ten,Hundred;printf("Input a number:");scanf("%d&q…...

31.Harmonyos Next仿uv-ui 组件NumberBox 步进器组件异步操作处理

Harmonyos Next仿uv-ui 组件NumberBox 步进器组件异步操作处理 文章目录 Harmonyos Next仿uv-ui 组件NumberBox 步进器组件异步操作处理1. 组件介绍2. 效果展示3. 异步操作处理3.1 异步初始化3.2 异步值更新 4. 完整示例代码5. 知识点讲解5.1 异步操作基础5.2 异步操作中的状态…...

MIPI电平标准详解

一、MIPI电平的定义与核心特性 MIPI&#xff08;Mobile Industry Processor Interface&#xff09; 是由 MIPI联盟 制定的移动设备接口标准&#xff0c;涵盖摄像头&#xff08;CSI&#xff09;、显示屏&#xff08;DSI&#xff09;、射频&#xff08;RFFE&#xff09;等多个领…...

使用位运算实现加法、减法、乘法和除法

使用位运算实现加法、减法、乘法和除法是一个经典的计算机科学问题。位运算通常用于低级程序设计和性能优化中&#xff0c;以下是如何用位运算实现这些基本数学运算。 加法 加法可以通过以下步骤实现&#xff1a; def add(a, b):while b ! 0:# 使用异或得到不考虑进位的加法…...