news 2026/10/29 2:06:53

Linux 学习笔记(三十六):并发进阶——读写锁 vs 自旋锁,到底怎么选?

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Linux 学习笔记(三十六):并发进阶——读写锁 vs 自旋锁,到底怎么选?

📌 核心结论(TL;DR):

  1. 选型口诀:多读少写选读写锁;极短临界区+多核选自旋锁;复杂逻辑选互斥锁(Mutex)。

  2. 读写锁三大铁律:读读共享、读写互斥、写写互斥。

  3. 自旋锁致命缺点:如果锁被占用时间较长,线程一直原地“忙等待(自旋)”,会白白把 CPU 跑满(浪费算力)。

  4. 饥饿问题:读写锁默认策略下,如果读者源源不断,写者可能会被“饿死”(长时间抢不到锁)。

一、 互斥锁的局限性

传统的互斥锁(Mutex)非常霸道:无论你是读取数据还是修改数据,只要有线程加锁,其他线程全部阻塞。但在实际开发中,往往是“多读少写”的场景。如果读操作也互斥,系统的并发性能将大打折扣。

二、 读写锁(Reader-Writer Lock)

读写锁专门针对多读少写场景设计,遵循三大核心规则:

  1. 读读共享:多个线程可以同时持有读锁。

  2. 读写互斥:写操作独占,此时不允许任何读操作。

  3. 写写互斥:多个写线程之间必须互斥。

核心 API:

  • pthread_rwlock_rdlock():加读锁。

  • pthread_rwlock_wrlock():加写锁。

  • pthread_rwlock_unlock():解锁。

(插入 rwlock_test.cc 代码)

#include <iostream> #include <pthread.h> #include <unistd.h> int shared_data = 0; pthread_rwlock_t rwlock; void* Reader(void* arg) { long id = (long)arg; while (true) { pthread_rwlock_rdlock(&rwlock); // 加读锁 std::cout << "读者-" << id << " 正在读取数据: " << shared_data << std::endl; sleep(1); pthread_rwlock_unlock(&rwlock); // 解锁 } return nullptr; } void* Writer(void* arg) { long id = (long)arg; while (true) { pthread_rwlock_wrlock(&rwlock); // 加写锁 shared_data++; std::cout << "写者-" << id << " 正在写入数据: " << shared_data << std::endl; sleep(1); pthread_rwlock_unlock(&rwlock); sleep(1); } return nullptr; }

三、 自旋锁(Spinlock)

自旋锁与 Mutex 的最大区别在于“等待”的方式:

  • Mutex发现锁被占用,会让线程进入睡眠状态(阻塞态),让出 CPU。醒来时需要上下文切换,开销大。

  • Spinlock发现锁被占用,会开启死循环原地自旋(忙等待),不放弃 CPU。

(插入 spinlock_test.cc 核心代码)

pthread_spinlock_t spinlock; // 自旋锁 void* route(void* arg) { while (true) { pthread_spin_lock(&spinlock); // 加自旋锁 if (ticket > 0) { // 极短的操作 ticket--; } else { pthread_spin_unlock(&spinlock); break; } pthread_spin_unlock(&spinlock); } }

四、 面试必考:如何避坑与选型?

  1. 自旋锁的坑:由于是在原地死循环,如果临界区代码很长,其他线程会白白消耗大量 CPU 资源(在单核 CPU 上尤为致命,因为自旋的线程占着 CPU,拿到锁的线程根本没法运行)。

  2. 读写锁的“写者饥饿”:在默认读者优先的策略下,如果一直有新的读者进来,写者可能永远无法获得写锁。可以通过设置PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP属性让写者优先。

  3. 选型总结:

    • 读操作多、写操作少 →→读写锁。

    • 临界区只有几条指令,且是多核 CPU →→自旋锁。

    • 临界区长、逻辑复杂 →→互斥锁 (Mutex)。

自我实战操作

咱们来写一段代码,用读写锁模拟“2 个读者,2 个写者”同时访问一个共享数据。

cd ~/linux_study mkdir rw_spin_test cd rw_spin_test
实战 1:读写锁测试
vim rwlock_test.cc

(输入以下代码)

#include <iostream> #include <pthread.h> #include <unistd.h> int shared_data = 0; // 共享数据 pthread_rwlock_t rwlock; // 读写锁 // 读者线程 void* Reader(void* arg) { long id = (long)arg; while (true) { pthread_rwlock_rdlock(&rwlock); // 加读锁 std::cout << "读者-" << id << " 正在读取数据: " << shared_data << std::endl; sleep(1); // 模拟读取耗时 pthread_rwlock_unlock(&rwlock); // 解锁 // sleep(1); // 打开这个注释,写者会饿死 } return nullptr; } // 写者线程 void* Writer(void* arg) { long id = (long)arg; while (true) { pthread_rwlock_wrlock(&rwlock); // 加写锁 shared_data++; std::cout << "写者-" << id << " 正在写入数据: " << shared_data << std::endl; sleep(1); // 模拟写入耗时 pthread_rwlock_unlock(&rwlock); // 解锁 sleep(1); // 写者给读者留点机会 } return nullptr; } int main() { pthread_rwlock_init(&rwlock, nullptr); pthread_t r1, r2, w1, w2; pthread_create(&r1, nullptr, Reader, (void*)1); pthread_create(&r2, nullptr, Reader, (void*)2); pthread_create(&w1, nullptr, Writer, (void*)1); pthread_create(&w2, nullptr, Writer, (void*)2); pthread_join(r1, nullptr); pthread_join(r2, nullptr); pthread_join(w1, nullptr); pthread_join(w2, nullptr); pthread_rwlock_destroy(&rwlock); return 0; }

编译运行:

g++ rwlock_test.cc -o rwlock_test -lpthread ./rwlock_test

👉现象:你会看到“读者-1”和“读者-2”经常一起打印(读读共享),但当“写者”打印时,两个读者必定是停住的(读写互斥)。

实战 2:自旋锁测试

(注意:自旋锁在单核虚拟机上测试效果不明显,但代码必须要会写)

vim spinlock_test.cc

(输入以下代码)

#include <iostream> #include <pthread.h> #include <unistd.h> int ticket = 1000; pthread_spinlock_t spinlock; // 自旋锁 void* route(void* arg) { long id = (long)arg; while (true) { pthread_spin_lock(&spinlock); // 加自旋锁 if (ticket > 0) { usleep(100); // 模拟极短的操作 printf("线程-%ld 卖出票: %d\n", id, ticket); ticket--; } else { pthread_spin_unlock(&spinlock); break; } pthread_spin_unlock(&spinlock); // 解锁 } return nullptr; } int main() { pthread_spin_init(&spinlock, PTHREAD_PROCESS_PRIVATE); pthread_t t1, t2, t3; pthread_create(&t1, nullptr, route, (void*)1); pthread_create(&t2, nullptr, route, (void*)2); pthread_create(&t3, nullptr, route, (void*)3); pthread_join(t1, nullptr); pthread_join(t2, nullptr); pthread_join(t3, nullptr); pthread_spin_destroy(&spinlock); return 0; }

(编译运行g++ spinlock_test.cc -o spinlock_test -lpthread && ./spinlock_test)

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

如何做学术研究、科研

做学术研究的核心是提出有价值的问题&#xff0c;并通过严谨的方法系统验证、输出可信成果&#xff0c;完整流程可分为5个核心阶段&#xff0c;覆盖从入门选题到成果落地的全链路。 &#x1f50d; 第一阶段&#xff1a;精准选题与文献调研 这是科研的起点&#xff0c;直接决定…

作者头像 李华
网站建设 2026/10/29 2:06:32

排障还在半夜喊老法师?C#接入豆包大模型,工控设备故障诊断与根因分析落地实战

做工控运维的都懂,现场最头疼的从来不是修设备,是找故障。 一条产线几十台设备,上百个测点,报警一响,红灯亮一片。老工程师在还好,扫一眼参数、听个声音,三五分钟就能定位根因;赶上新人值班,对着满屏数据和报警记录,半天摸不着头绪,只能挨个打电话请教。经验全装在人…

作者头像 李华
网站建设 2026/10/29 2:05:34

【轻AI越影视觉开发板】开发资源全景:从芯片规格到 18 个官方 Sample 的完整地图

本文把轻AI越影视觉开发板能用的资料一次性梳理清楚&#xff1a;芯片什么水平、板子有什么接口、代码在哪个仓、18 个 sample 各解决什么问题、文档该按什么顺序读。适合刚拿到板子、准备上手的人直接当地图用。 一、先搞清楚这块板是什么 轻AI越影视觉开发板的核心是一颗定位…

作者头像 李华
网站建设 2026/10/29 2:04:01

京存信创存储:全栈国产化解决方案

一、政企信创存储普遍存在的行业痛点在政务、医疗、广电、科研、媒资、AI 仿真、测绘遥感等行业数字化建设进程中&#xff0c;大量单位在存储设备选型阶段会遇到各类棘手问题&#xff1a;存储设备软硬件非全栈国产化&#xff0c;项目密评、等保、信创验收无法通过&#xff0c;存…

作者头像 李华
网站建设 2026/10/29 2:03:29

创建、导入测试框架与属性设置

1. 概念讲解 测试框架是与设计模型隔离的独立工作区,你可以为整个模型创建,也可以为模型中的一个或多个组件创建,还可以从一个独立测试模型导入现成的框图作为测试框架。 两种创建入口: 为顶层模型(含子系统模型与模型引用模型)创建时,直接在 Simulink 模型画布空白处…

作者头像 李华