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

C++读写锁以及实现方式

文章目录

      • 【C++专题】读写锁(Reader-Writer Lock)原理与实现方式(含C++11/20实践)
        • 一、读写锁核心概念
          • 1. **什么是读写锁?**
          • 2. **读写锁 vs 互斥锁**
        • 二、C++中的读写锁实现方式
      • 方案一:POSIX 读写锁(pthread_rwlock)
          • 1. **关键接口**
          • 2. **使用示例:缓存读取与更新**
          • 3. **注意事项**
      • 方案二:C++20 共享互斥锁(std::shared_mutex)
          • 1. **核心类**
          • 2. **使用示例:线程安全的计数器**
          • 3. **优势**
      • 方案三:自定义读写锁(基于互斥量与条件变量)
          • 1. **实现原理**
          • 2. **代码实现(简化版)**
          • 3. **关键点**
        • 三、读写锁的性能与陷阱
          • 1. **性能对比**
          • 2. **常见陷阱**
        • 四、最佳实践建议
        • 五、总结

【C++专题】读写锁(Reader-Writer Lock)原理与实现方式(含C++11/20实践)

一、读写锁核心概念
1. 什么是读写锁?

读写锁是一种同步机制,用于控制多个线程对共享资源的访问,其核心特性:

  • 读锁(共享锁):允许多个线程同时获取,适用于只读操作(如读取配置文件、缓存查询)。
  • 写锁(排他锁):仅允许单个线程获取,适用于写操作(如修改数据、更新缓存),此时其他线程(包括读线程)均被阻塞。

适用场景:读多写少的场景(如数据库缓存、日志系统),相比普通互斥锁(Mutex)能显著提升并发性。

2. 读写锁 vs 互斥锁
维度互斥锁(Mutex)读写锁(Reader-Writer Lock)
读操作并发同一时间仅1线程访问允许多线程同时读
写操作开销低(简单加锁)高(需协调读/写线程)
典型场景读写均衡或写多读少读多写少(如配置中心、缓存)
二、C++中的读写锁实现方式

方案一:POSIX 读写锁(pthread_rwlock)

适用范围:Linux/macOS等UNIX-like系统,C++11之前的主流方案。

1. 关键接口
#include <pthread.h>// 初始化读写锁
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);// 加读锁(阻塞式)
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
// 尝试加读锁(非阻塞,成功返回0,失败返回EBUSY)
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);// 加写锁(阻塞式)
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
// 尝试加写锁(非阻塞)
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);// 解锁
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);// 销毁锁
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
2. 使用示例:缓存读取与更新
#include <iostream>
#include <pthread.h>
#include <vector>
#include <thread>pthread_rwlock_t rwlock;
std::vector<int> cache;// 读线程:获取读锁,读取缓存
void read_cache(int id) {pthread_rwlock_rdlock(&rwlock);std::cout << "Thread " << id << " reading: ";for (auto val : cache) {std::cout << val << " ";}std::cout << std::endl;pthread_rwlock_unlock(&rwlock);
}// 写线程:获取写锁,更新缓存
void update_cache(int id, std::vector<int> new_data) {pthread_rwlock_wrlock(&rwlock);cache = new_data;std::cout << "Thread " << id << " updated cache." << std::endl;pthread_rwlock_unlock(&rwlock);
}int main() {pthread_rwlock_init(&rwlock, nullptr);cache = {1, 2, 3};// 启动3个读线程std::thread readers[3];for (int i = 0; i < 3; ++i) {readers[i] = std::thread(read_cache, i);}// 启动1个写线程(会等待读线程释放锁)std::thread writer(update_cache, 4, {4, 5, 6});// 等待所有线程结束for (auto& th : readers) th.join();writer.join();pthread_rwlock_destroy(&rwlock);return 0;
}
3. 注意事项
  • 初始化与销毁:需调用pthread_rwlock_initpthread_rwlock_destroy,避免内存泄漏。
  • 线程安全:写锁具有更高优先级,避免读线程饥饿(某些实现支持“写优先”策略)。

方案二:C++20 共享互斥锁(std::shared_mutex)

适用范围:C++20及以上标准,跨平台(Windows/Linux/macOS)。

1. 核心类
  • std::shared_mutex:底层实现读写锁语义。
  • std::shared_lock:用于获取读锁(共享锁)。
  • std::unique_lock:用于获取写锁(排他锁)。
2. 使用示例:线程安全的计数器
#include <iostream>
#include <shared_mutex>
#include <thread>
#include <vector>std::shared_mutex rw_mutex;
int counter = 0;// 读操作:统计计数器值
void read_counter(int id) {std::shared_lock<std::shared_mutex> lock(rw_mutex); // 自动获取读锁std::cout << "Thread " << id << ": Counter = " << counter << std::endl;
}// 写操作:增加计数器值
void increment_counter(int id, int times) {std::unique_lock<std::shared_mutex> lock(rw_mutex); // 自动获取写锁for (int i = 0; i < times; ++i) {counter++;}std::cout << "Thread " << id << " updated counter to " << counter << std::endl;
}int main() {std::vector<std::thread> threads;// 启动5个读线程for (int i = 0; i < 5; ++i) {threads.emplace_back(read_counter, i);}// 启动2个写线程for (int i = 5; i < 7; ++i) {threads.emplace_back(increment_counter, i, 100);}for (auto& th : threads) th.join();return 0;
}
3. 优势
  • RAII风格:通过std::shared_lockstd::unique_lock自动管理锁的生命周期,避免忘记解锁。
  • 跨平台兼容:无需依赖POSIX接口,可在Windows(如Visual Studio 2019+)和UNIX系统上编译。

方案三:自定义读写锁(基于互斥量与条件变量)

适用场景:需要理解底层原理,或在不支持C++20的环境中模拟读写锁。

1. 实现原理
  • 读计数器:记录当前获取读锁的线程数,无写锁时允许多线程读。
  • 写锁标志:表示是否有线程持有写锁,写锁获取时阻塞所有读线程。
  • 条件变量:用于写线程等待读线程释放锁,或读线程等待写锁释放。
2. 代码实现(简化版)
#include <mutex>
#include <condition_variable>class ReadWriteLock {
private:std::mutex mtx;std::condition_variable read_cv, write_cv;int reader_count = 0;bool writer_active = false;public:// 获取读锁void lock_read() {std::unique_lock<std::mutex> lock(mtx);// 等待写锁释放read_cv.wait(lock, [this]() { return !writer_active; });reader_count++;}// 释放读锁void unlock_read() {std::unique_lock<std::mutex> lock(mtx);reader_count--;if (reader_count == 0) {write_cv.notify_one(); // 唤醒等待的写线程}}// 获取写锁void lock_write() {std::unique_lock<std::mutex> lock(mtx);// 等待所有读线程释放且无写锁write_cv.wait(lock, [this]() { return reader_count == 0 && !writer_active; });writer_active = true;}// 释放写锁void unlock_write() {std::unique_lock<std::mutex> lock(mtx);writer_active = false;read_cv.notify_all(); // 唤醒所有读线程write_cv.notify_one(); // 唤醒可能等待的写线程}
};
3. 关键点
  • 读锁加锁:通过条件变量等待写锁释放,允许多线程同时持有读锁(通过reader_count计数)。
  • 写锁加锁:必须等待所有读锁释放(reader_count==0)且无其他写锁。
  • 优先级策略:此实现为“读优先”,写线程可能饥饿,可通过增加写等待标志优化为“写优先”。
三、读写锁的性能与陷阱
1. 性能对比
操作类型POSIX读写锁std::shared_mutex自定义读写锁(读优先)
读锁加锁耗时
写锁加锁耗时
适用场景系统级开发现代C++应用学习/定制化需求
2. 常见陷阱
  • 死锁:读线程持有读锁时尝试获取写锁,或写线程同时获取多个锁(需避免嵌套加锁)。
  • 饥饿:读线程过多导致写线程长期无法获取锁(可通过“写优先”策略缓解)。
  • 错误解锁:读锁释放时未正确更新计数器,或写锁释放时未通知等待线程。
四、最佳实践建议
  1. 优先使用C++20标准库std::shared_mutexstd::shared_lock提供简洁、安全的读写锁实现,推荐用于新项目。
  2. 明确锁作用域:使用RAII风格的lock_guardunique_lock,避免锁的作用域泄漏。
  3. 性能测试:在高并发场景下,通过基准测试(如Google Benchmark)比较读写锁与无锁编程(如原子操作)的性能差异。
  4. 文档与注释:在代码中注明锁的类型(读锁/写锁)和保护的共享资源,提升可维护性。
五、总结

读写锁是多线程编程中提升读性能的关键工具,其核心在于分离读/写操作的并发策略:

  • POSIX读写锁:适用于UNIX系统的高性能场景,但需手动管理锁生命周期。
  • C++20共享互斥锁:跨平台、安全、简洁,推荐现代C++项目使用。
  • 自定义实现:帮助理解底层原理,但需谨慎处理线程安全和性能问题。

合理使用读写锁能显著优化读多写少场景的并发性,但需注意避免死锁和饥饿问题,结合具体业务场景选择最优方案。

参考资料

  • C++标准文档:std::shared_mutex
  • POSIX文档:pthread_rwlock
  • 《C++ Concurrency in Action》

相关文章:

C++读写锁以及实现方式

文章目录 【C专题】读写锁&#xff08;Reader-Writer Lock&#xff09;原理与实现方式&#xff08;含C11/20实践&#xff09;一、读写锁核心概念1. **什么是读写锁&#xff1f;**2. **读写锁 vs 互斥锁** 二、C中的读写锁实现方式 方案一&#xff1a;POSIX 读写锁&#xff08;p…...

Electron-vite【实战】MD 编辑器 -- 文件列表(含右键快捷菜单,重命名文件,删除本地文件,打开本地目录等)

最终效果 页面 src/renderer/src/App.vue <div class"dirPanel"><div class"panelTitle">文件列表</div><div class"searchFileBox"><Icon class"searchFileInputIcon" icon"material-symbols-light:…...

华为云Flexus+DeepSeek征文|华为云Flexus云服务器X实例上部署Dify:打造高效的开源大语言模型应用开发平台

目录 前言 1 Dify与华为云部署概述 1.1 什么是 Dify 1.2 华为云与 Flexus 云服务器的优势 2 云服务器部署 Dify 的步骤详解 2.1 模板选择 2.2 参数配置 2.3 资源栈设置 2.4 确认部署信息并执行 3 部署成功后的操作与平台使用指南 3.1 访问平台 3.2 设置管理员账号 …...

[git每日一句]Your branch is up to date with ‘origin/master‘

这句话是 Git 版本控制系统的提示信息&#xff0c;意思是&#xff1a; "你当前所在的分支已经与远程仓库&#xff08;origin&#xff09;的 master 分支同步&#xff0c;没有需要推送的提交。" 详细解释&#xff1a; Your branch - 指你当前所在的本地分支 is up …...

高密爆炸警钟长鸣:AI为化工安全戴上“智能护盾”

一、高密爆炸&#xff1a;一声巨响&#xff0c;撕开化工安全“伤疤” 2025年5月27日&#xff0c;山东高密友道化学有限公司的车间爆炸声&#xff0c;像一把利刃划破了化工行业的平静。剧烈的冲击波将车间夷为平地&#xff0c;黑色蘑菇云腾空而起&#xff0c;刺鼻的化学气味弥漫…...

机器人学基础——正运动学(理论推导及c++实现)

机器人正运动学 机器人正运动学一般是指从机器人的关节位置到基于参考坐标系下末端执行器的位置。 平移变换和旋转变换 平移变换 假设我们有两个坐标系A和B&#xff0c;坐标系A与B的方位相同&#xff0c;xyz轴的指向都是一致的&#xff0c;即没有旋转变换。有一点p&#xf…...

[网页五子棋][对战模块]处理连接成功,通知玩家就绪,逻辑问题(线程安全,先手判定错误)

文章目录 处理连接成功通知玩家就绪逻辑图问题 1&#xff1a;线程安全问题 2&#xff1a;先手判定错误两边都是提示&#xff1a;轮到对方落子![image.png](https://i-blog.csdnimg.cn/img_convert/c570cd26eadbe87ed467bc4edaa7945e.png) 处理连接成功 实现 GameAPI 的 afterC…...

TensorFlow Extended (TFX) 生产环境模型版本控制与回滚实战指南

TFX 版本控制核心架构 TFX 通过以下组件构建完整的模型生命周期管理系统&#xff1a; ​​ML Metadata (MLMD)​​&#xff1a;记录所有实验和管道的元数据​​Pusher 组件​​&#xff1a;负责模型部署与版本标记​​Model Registry​​&#xff1a;集中式模型存储库&#x…...

【Web应用】若依框架:基础篇11功能详解-系统接口

文章目录 ⭐前言⭐一、课程讲解⭐二、自己动手实操⭐总结 标题详情作者JosieBook头衔CSDN博客专家资格、阿里云社区专家博主、软件设计工程师博客内容开源、框架、软件工程、全栈&#xff08;,NET/Java/Python/C&#xff09;、数据库、操作系统、大数据、人工智能、工控、网络、…...

【Docker项目实战篇】Docker部署PDF查看器PdfDing

【Docker项目实战篇】Docker部署PDD查看器PdfDing 一、PdfDing介绍1.1 PdfDing简介1.2 PdfDing主要特点1.3 主要使用场景 二、本次实践规划2.1 本地环境规划2.2 本次实践介绍 三、本地环境检查3.1 检查Docker服务状态3.2 检查Docker版本3.3 检查docker compose 版本 四、下载Pd…...

Redis 常用数据类型和命令使用

目录 1 string 2 hash 3 list 4 set集合 5 zset有序集合 1 string 值可以是字符串、数字和二进制的value&#xff0c;值最大不能超过512MB 应用场景&#xff1a; 应用程序缓存 计数器 web共享session 限速 1.1 设置单个键值 set <key> value [EX seconds|PX…...

【Linux系统】第八节—进程概念(上)—冯诺依曼体系结构+操作系统+进程及进程状态+僵尸进程—详解!

hi&#xff0c;我是云边有个稻草人 偶尔中二的博主^(*&#xffe3;(oo)&#xffe3;)^&#xff0c;与你分享专业知识&#xff0c;祝博主们端午节快乐&#xff01; Linux—本节博客所属专栏—持续更新中—欢迎订阅&#xff01; 目录 一、冯诺依曼体系结构 二、操作系统(Opera…...

WPF 全局加载界面、多界面实现渐变过渡效果

WPF 全局加载界面与渐变过渡效果 完整实现方案 MainWindow.xaml <Window x:Class"LoadingScreenDemo.MainWindow"xmlns"http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x"http://schemas.microsoft.com/winfx/2006/xaml&quo…...

WebSocket与实时对话式AI服务的集成

WebSocket与实时对话式AI服务的集成 在现代对话式AI系统中,传统的HTTP请求-响应模型已难以满足实时交互的体验需求。特别是用户对响应速度、逐字输出、会话上下文保持等方面提出更高要求时,需要一种能够建立持久连接并支持双向通信的协议。WebSocket正是在这一背景下,成为A…...

【xmb】】内部文档148344599

这里写自定义目录标题 CyberDog 2 仿真智能物流配送系统 – 初赛设计报告摘要目录1 引言2 任务与需求分析3 系统总体设计4 核心算法与模块实现5 仿真测试与结果分析6 结论与展望 CyberDog 2 仿真智能物流配送系统 – 初赛设计报告 团队名称&#xff1a; &#xff08;晚点写&am…...

MobaXterm国内下载与安装使用教程

MobaXterm是一款为 Windows 用户量身打造的远程终端工具&#xff0c;它将多种网络功能集成在一个轻量级、便携式的界面中&#xff0c;尤其适合需要频繁与远程主机交互的开发者、系统运维工程师以及科研技术人员。无论是管理 Linux 服务器、远程执行命令&#xff0c;还是图形化运…...

数据结构——优先级队列(PriorityQueue)

1.优先级队列 优先级队列可以看作队列的另一个版本&#xff0c;队列的返回元素是由是由插入顺序决定的&#xff0c;先进先出嘛&#xff0c;但是有时我们可能想要返回优先级较高的元素&#xff0c;比如最大值&#xff1f;这种场景下就由优先级队列登场。 优先级队列底层是由堆实…...

代谢组数据分析(二十六):LC-MS/MS代谢组学和脂质组学数据的分析流程

禁止商业或二改转载,仅供自学使用,侵权必究,如需截取部分内容请后台联系作者! 文章目录 介绍加载R包依赖包安装包加载需要的R包数据下载以及转换mzML数据预处理代谢物注释LipidFinder过滤MultiABLER数据预处理过滤补缺失值对数变换数据标准化下游数据分析总结系统信息参考介…...

服务器上用脚本跑python深度学习的注意事项(ubantu系统)

bash: $\r: command not found 问题原因&#xff1a; 出现 bash: $\r: command not found 以及路径中出现 \r 通常是因为脚本文件是在Windows系统下编辑&#xff0c;然后在Linux&#xff08;如Ubuntu&#xff09;系统中运行。在Windows系统中&#xff0c;文本文件的换行符是 \…...

【ARM】【FPGA】【硬件开发】Chapter.1 AXI4总线协议

Chapter.1 AXI4总线协议 作者&#xff1a;齐花Guyc(CAUC) 一、总线介绍 AXI4总线 AXI4总线就像是SoC内部的“高速公路”&#xff0c;负责在不同硬件模块之间高效传输数据。 AXI4协议通过 5个独立通道 传输数据和控制信号&#xff0c;每个通道都有自己的信号线&#xff0c;互…...

青少年编程与数学 02-020 C#程序设计基础 10课题、桌面应用开发

青少年编程与数学 02-020 C#程序设计基础 10课题、桌面应用开发 一、桌面应用1. 主要特点2. 常见类型3. 优势4. 局限性 二、开发步骤1. 准备工作2. 创建项目3. 开发应用4. 运行调试5. 打包发布 三、Windows 窗体应用&#xff08;一&#xff09;定义&#xff08;二&#xff09;特…...

把 jar 打包成 exe

1. 把自己的项目先正常打成jar包 2. 使用exe4j工具将jar转换为exe 2.1 exe4j下载地址&#xff1a;https://www.ej-technologies.com/download/exe4j/files 2.2 下载完成之后激活 2.3 可以点击Change License&#xff0c;输入秘钥L-g782dn2d-1f1yqxx1rv1sqd 2.4 直接下一步…...

【目标检测】检测网络中neck的核心作用

1. neck最主要的作用就是特征融合&#xff0c;融合就是将具有不同大小感受野的特征图进行了耦合&#xff0c;从而增强了特征图的表达能力。 2. neck决定了head的数量&#xff0c;进而潜在决定了不同尺度样本如何分配到不同的head&#xff0c;这一点可以看做是将整个网络的多尺…...

【经验】Ubuntu中设置terminator的滚动行数、从Virtualbox复制到Windows时每行后多一空行

1、设置terminator的滚动行数 1.1 问题描述 在终端 terminator 中&#xff0c;调试程序时&#xff0c;只能查看有限行数的打印日志&#xff0c;大约是500行&#xff0c;怎么能增加行数 1.2 解决方法 1&#xff09;安装terminator sudo apt install terminator和 terminato…...

使用微软最近开源的WSL在Windows上优雅的运行Linux

install wsl https://github.com/microsoft/WSL/releases/download/2.4.13/wsl.2.4.13.0.x64.msi install any distribution from microsoft store, such as kali-linux from Kali office website list of distribution PS C:\Users\50240> wsl -l -o 以下是可安装的有…...

HackMyVM-Teacher

信息搜集 主机发现 ┌──(kali㉿kali)-[~] └─$ nmap -sn 192.168.43.0/24 Starting Nmap 7.95 ( https://nmap.org ) at 2025-06-01 01:02 EDT Nmap scan report for 192.168.43.1 Host is up (0.0084s latency). MAC Address: C6:45:66:05:91:88 (Unknow…...

BugKu Web渗透之矛盾

开启场景&#xff0c;打开网页。发现是一段php代码。 这段代码也很好理解&#xff0c;就是get方式传参num&#xff0c;如果num不是数字类型&#xff0c;那么输出num的值&#xff0c;并且num1时&#xff0c;输出flag的值。 首先看看is_numeric的意思。 开始我想到了使用科学技术…...

hot100 -- 4.子串系列

1.和为 K 的子数组 问题&#xff1a; 给你一个整数数组 nums 和一个整数 k &#xff0c;请你统计并返回 该数组中和为 k 的子数组的个数 。 子数组是数组中元素的连续非空序列。 方法1&#xff1a;暴力枚举 # 方法1&#xff1a;暴力枚举&#xff08;遍历子数组起点和终点&…...

Python实现P-PSO优化算法优化卷积神经网络CNN回归模型项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档&#xff09;&#xff0c;如需数据代码文档可以直接到文章最后关注获取。 1.项目背景 随着人工智能和深度学习技术的快速发展&#xff0c;卷积神经网络&#xff08;CNN&#xff09;在图像分类、目标检测…...

ssm 学习笔记day03

环境搭建 spring配置数据库 1.在pom.xml安装相应的依赖 2.在properties里面配置数据库的相关信息&#xff0c;需要强调的一点是&#xff0c;一定不要在properties里面添加任何空格&#xff0c;否则就会像我一样搞了两小时&#xff0c;数据一直报错&#xff0c;然后发现是空格的…...