news 2026/8/31 16:08:18

读写锁详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
读写锁详解

文章目录

  • 为什么普通互斥锁不够?
  • 读写锁的核心规则
  • 读者优先和写者优先
    • 读者优先
    • 写者优先
  • Linux读写锁接口
    • 初始化和销毁
    • 加读锁、加写锁和解锁
    • 非阻塞尝试
    • 简单用例
  • 读写锁和互斥锁怎么选?

当多个线程访问同一份共享数据时,读操作之间通常不会互相修改数据,而写操作必须独占资源。读写锁(Read-Write Lock)针对「读多写少」场景设计:多个读者可以并发读取,但写者进入临界区时必须排他访问。

为什么普通互斥锁不够?

假设共享数据只有一个整数,线程分为两类:

  • 读者:只读取数据,不修改数据。
  • 写者:修改数据,修改期间不允许其他线程读取或写入。

如果所有操作都使用普通互斥锁,安全性没有问题,但多个读者也必须排队:

读者 2普通互斥锁读者 1读者 2普通互斥锁读者 1加锁获得锁并读取等待解锁获得锁并读取

读者之间没有数据修改冲突,却被迫串行执行。读写锁能把这种关系表达得更准确:读者共享,写者独占。

读写锁的核心规则

模式同时允许的线程典型用途
读锁(共享锁)多个读者查询、遍历、读取配置
写锁(独占锁)一个写者,且不能有读者更新、插入、删除

第一个读者申请读锁

其他读者申请读锁

最后一个读者释放读锁

写者申请写锁

写者释放写锁

写者申请写锁

所有读者退出

空闲

读者占用

写者占用

写者等待

必须牢记:

  1. 写者持有写锁时,不能有其他读者或写者进入临界区。
  2. 读者持有读锁时,其他读者可以并发进入,但写者必须等待。
  3. 每次成功加锁都必须对应一次解锁。

读者优先和写者优先

读者优先

只要当前有读者,新到达的读者通常可以继续获得读锁。读吞吐量较高,但如果读者持续到达,写者可能长时间得不到机会,形成「写者饥饿」。

读者进入

更多读者进入

写者持续等待

读者全部退出后写者进入

写者优先

一旦有写者等待,后续读者会被暂缓,让写者尽快完成更新。这样可以降低写者等待时间,但写者持续到达时,读者可能长时间等待。

读者正在读取

写者到达并等待

后续读者暂缓

已有读者退出

写者进入并更新

Linux读写锁接口

初始化和销毁

pthread_rwlock_t rwlock;pthread_rwlock_init(&rwlock,nullptr);// 创建并运行线程pthread_rwlock_destroy(&rwlock);

销毁前必须确保没有线程仍在使用该锁。

加读锁、加写锁和解锁

pthread_rwlock_rdlock(&rwlock);// 申请读锁// 读取共享数据pthread_rwlock_unlock(&rwlock);pthread_rwlock_wrlock(&rwlock);// 申请写锁// 修改共享数据pthread_rwlock_unlock(&rwlock);

读者调用rdlock,多个读者可以同时持有读锁;写者调用wrlock,必须等待所有读者退出,并独占临界区。

非阻塞尝试

intrc1=pthread_rwlock_tryrdlock(&rwlock);intrc2=pthread_rwlock_trywrlock(&rwlock);

如果锁不可用,函数会立即返回错误码。使用try*时必须处理失败分支,不能把「未获得锁」当成「已经进入临界区」。

简单用例

#include<pthread.h>intshared_data=0;pthread_rwlock_t rwlock;void*reader(void*){pthread_rwlock_rdlock(&rwlock);intvalue=shared_data;pthread_rwlock_unlock(&rwlock);returnnullptr;}void*writer(void*){pthread_rwlock_wrlock(&rwlock);++shared_data;pthread_rwlock_unlock(&rwlock);returnnullptr;}

代码:

#include<pthread.h>#include<unistd.h>#include<atomic>#include<cstdlib>#include<iostream>#include<mutex>#include<random>#include<thread>#include<vector>namespace{constexprintkReaderCount=4;constexprintkWriterCount=2;constexprintkRoundsPerThread=8;intshared_data=0;pthread_rwlock_t rwlock;std::atomic<int>active_readers{0};std::atomic<int>active_writers{0};std::atomic<int>max_concurrent_readers{0};std::atomic<int>max_concurrent_writers{0};std::atomic<int>overlap_count{0};std::atomic<int>write_count{0};std::mutex output_mutex;voidupdate_max(std::atomic<int>&target,intvalue){intprevious=target.load(std::memory_order_relaxed);while(previous<value&&!target.compare_exchange_weak(previous,value,std::memory_order_relaxed,std::memory_order_relaxed)){}}voidreader_task(intid){for(intround=0;round<kRoundsPerThread;++round){if(pthread_rwlock_rdlock(&rwlock)!=0){std::lock_guard<std::mutex>lock(output_mutex);std::cerr<<"读者 "<<id<<" 获取读锁失败\n";return;}constintreaders=active_readers.fetch_add(1)+1;update_max(max_concurrent_readers,readers);if(active_writers.load()!=0){++overlap_count;}constintvalue=shared_data;usleep(5'000);active_readers.fetch_sub(1);pthread_rwlock_unlock(&rwlock);if(round==0){std::lock_guard<std::mutex>lock(output_mutex);std::cout<<"读者-"<<id<<" 读取到 "<<value<<'\n';}std::this_thread::yield();}}voidwriter_task(intid){std::mt19937generator(static_cast<unsigned>(id+17));std::uniform_int_distribution<int>distribution(1,9);for(intround=0;round<kRoundsPerThread;++round){if(pthread_rwlock_wrlock(&rwlock)!=0){std::lock_guard<std::mutex>lock(output_mutex);std::cerr<<"写者 "<<id<<" 获取写锁失败\n";return;}constintwriters=active_writers.fetch_add(1)+1;update_max(max_concurrent_writers,writers);if(writers!=1||active_readers.load()!=0){++overlap_count;}shared_data+=distribution(generator);++write_count;constintvalue=shared_data;usleep(8'000);active_writers.fetch_sub(1);pthread_rwlock_unlock(&rwlock);if(round==0){std::lock_guard<std::mutex>lock(output_mutex);std::cout<<"写者-"<<id<<" 写入后为 "<<value<<'\n';}std::this_thread::yield();}}}// namespaceintmain(){if(pthread_rwlock_init(&rwlock,nullptr)!=0){std::cerr<<"读写锁初始化失败\n";returnEXIT_FAILURE;}std::cout<<"配置:读者 "<<kReaderCount<<" 个,写者 "<<kWriterCount<<" 个,每个线程 "<<kRoundsPerThread<<" 轮\n";std::vector<std::thread>readers;std::vector<std::thread>writers;readers.reserve(kReaderCount);writers.reserve(kWriterCount);for(inti=0;i<kReaderCount;++i){readers.emplace_back(reader_task,i);}for(inti=0;i<kWriterCount;++i){writers.emplace_back(writer_task,i);}for(auto&thread:readers){thread.join();}for(auto&thread:writers){thread.join();}pthread_rwlock_destroy(&rwlock);constintexpected_writes=kWriterCount*kRoundsPerThread;constboolpassed=max_concurrent_writers.load()==1&&overlap_count.load()==0&&write_count.load()==expected_writes;std::cout<<"最大并发读者数:"<<max_concurrent_readers.load()<<'\n';std::cout<<"最大并发写者数:"<<max_concurrent_writers.load()<<'\n';std::cout<<"读写重叠次数:"<<overlap_count.load()<<'\n';std::cout<<"写入次数:"<<write_count.load()<<'\n';std::cout<<"最终共享数据:"<<shared_data<<'\n';std::cout<<"验证结果:"<<(passed?"通过":"失败")<<'\n';returnpassed?EXIT_SUCCESS:EXIT_FAILURE;}

运行结果:

配置:读者 4 个,写者 2 个,每个线程 8 轮 读者-0 读取到 0 读者-2 读取到 0 读者-1 读取到 0 读者-3 读取到 0 写者-0 写入后为 3 写者-1 写入后为 43 最大并发读者数:4 最大并发写者数:1 读写重叠次数:0 写入次数:16 最终共享数据:75 验证结果:通过

读写锁和互斥锁怎么选?

条件更适合的工具
临界区很短,读写比例接近std::mutexpthread_mutex_t
读操作远多于写操作,读者可以安全并发pthread_rwlock_t
需要 C++ 风格的共享互斥量std::shared_mutex
共享资源支持无锁算法原子操作或专用并发容器

读写锁不一定比互斥锁快。它需要维护读者计数和等待队列;如果临界区很短、竞争不明显,普通互斥锁可能更简单。选择前应使用真实负载进行测量。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/31 16:08:10

有智慧的模型聊天会上瘾:本地部署与多轮对话技术拆解

玉伯谈 AI 时有个判断很值得琢磨&#xff1a;有智慧的模型聊天会上瘾。这句话放在技术语境里看&#xff0c;它点出的其实是大语言模型体验的分水岭——不是“能不能答对”&#xff0c;而是“能不能让人一直聊下去”。很多模型能给出标准答案&#xff0c;但只有少数模型能在多轮…

作者头像 李华
网站建设 2026/8/31 16:07:41

MOTIC显微镜图像采集无U盘版部署与使用全攻略

简介&#xff1a;MOTIC图像采集无U盘版motic 2.0是一款面向生物学、医学等科研一线人员的专业显微成像软件&#xff0c;专为搭配MOTIC系列CCD摄像头使用而设计&#xff0c;解决传统显微图像采集依赖U盘中转、操作繁琐、数据易丢失等痛点&#xff0c;显著提升活细胞动态观察、组…

作者头像 李华
网站建设 2026/8/31 16:05:59

Linux初学记第六章--fork

复制进程fork通过上一章&#xff0c;我们大概了解了以下进程&#xff0c;今天我们来讲进程中的复制进程fork()我们通过打开帮助书册得知了有关于它的一些信息:()中是2&#xff0c;证明是系统调用&#xff0c;通过create那一行可以知道fork的作用就是创建一个子进程&#xff0c;…

作者头像 李华
网站建设 2026/8/31 16:04:07

SSM框架健身房管理系统开发实战:架构设计与数据库核心方案

简介&#xff1a;本资源是一套基于SSM&#xff08;SpringSpringMVCMyBatis&#xff09;框架开发的B/S架构健身房管理系统完整源码&#xff0c;面向计算机、电子信息工程等专业的本科生及高职高专学生&#xff0c;适用于毕业设计、课程设计与期末大作业等实践场景。系统涵盖会员…

作者头像 李华
网站建设 2026/8/31 16:02:15

【计算机毕业设计】基于 YOLOv8 的口罩检测 Web 系统

基于 YOLOv8 的口罩检测 Web 系统 一、项目简介 本项目基于 YOLOv8 实现口罩佩戴状态检测&#xff0c;并在原有 Python / PyQt 程序基础上提供本机 Web 使用方式。项目包含 Web 入口、图片视频摄像头检测、模型训练、训练结果可视化、模型文件管理和本地检测记录等能力&#…

作者头像 李华
网站建设 2026/8/31 16:02:14

上帝视角揭秘:OpenCV透视变换与单应性矩阵实现鸟瞰图实战

各位开发者朋友&#xff0c;大家好。今天我们来聊一个在计算机视觉与自动驾驶领域经常听到的酷炫名词—— God‘s Eye View 。第一次听到这个名字&#xff0c;可能觉得它带有一些科幻色彩&#xff0c;仿佛真的拥有了俯瞰世界的“上帝视角”。实际上&#xff0c;在视觉技术领域…

作者头像 李华