📌 核心结论(TL;DR):
选型口诀 :多读少写选读写锁 ;极短临界区+多核选自旋锁 ;复杂逻辑选互斥锁(Mutex)。
读写锁三大铁律:读读共享、读写互斥、写写互斥。
自旋锁致命缺点:如果锁被占用时间较长,线程一直原地"忙等待(自旋)",会白白把 CPU 跑满(浪费算力)。
饥饿问题:读写锁默认策略下,如果读者源源不断,写者可能会被"饿死"(长时间抢不到锁)。
一、 互斥锁的局限性
传统的互斥锁(Mutex)非常霸道:无论你是读取数据还是修改数据,只要有线程加锁,其他线程全部阻塞。但在实际开发中,往往是"多读少写"的场景。如果读操作也互斥,系统的并发性能将大打折扣。
二、 读写锁(Reader-Writer Lock)
读写锁专门针对多读少写场景设计,遵循三大核心规则:
-
读读共享:多个线程可以同时持有读锁。
-
读写互斥:写操作独占,此时不允许任何读操作。
-
写写互斥:多个写线程之间必须互斥。
核心 API:
-
pthread_rwlock_rdlock():加读锁。 -
pthread_rwlock_wrlock():加写锁。 -
pthread_rwlock_unlock():解锁。
(插入 rwlock_test.cc 代码)
cpp
#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 核心代码)
cpp
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);
}
}
四、 面试必考:如何避坑与选型?
-
自旋锁的坑:由于是在原地死循环,如果临界区代码很长,其他线程会白白消耗大量 CPU 资源(在单核 CPU 上尤为致命,因为自旋的线程占着 CPU,拿到锁的线程根本没法运行)。
-
读写锁的"写者饥饿" :在默认读者优先的策略下,如果一直有新的读者进来,写者可能永远无法获得写锁。可以通过设置
PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP属性让写者优先。 -
选型总结:
-
读操作多、写操作少 →→ 读写锁。
-
临界区只有几条指令,且是多核 CPU →→ 自旋锁。
-
临界区长、逻辑复杂 →→ 互斥锁 (Mutex)。
-
自我实战操作
咱们来写一段代码,用读写锁模拟"2 个读者,2 个写者"同时访问一个共享数据。
bash
cd ~/linux_study
mkdir rw_spin_test
cd rw_spin_test
实战 1:读写锁测试
bash
vim rwlock_test.cc
(输入以下代码)
cpp
#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;
}
编译运行:
bash
g++ rwlock_test.cc -o rwlock_test -lpthread
./rwlock_test
👉 现象:你会看到"读者-1"和"读者-2"经常一起打印(读读共享),但当"写者"打印时,两个读者必定是停住的(读写互斥)。

实战 2:自旋锁测试
(注意:自旋锁在单核虚拟机上测试效果不明显,但代码必须要会写)
bash
vim spinlock_test.cc
(输入以下代码)
cpp
#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)
