i007.cc

i007.cc

优先队列-降维打击

05.价值资料

std::shared_mutex 及其用法

std::shared_mutex 是 C++17 引入的读写锁(共享互斥锁),专门用于读多写少场景。

普通 std::mutex 是独占锁:不管读还是写,同一时间只允许一个线程访问。

shared_mutex 支持两种模式:

  1. 共享模式(读锁):多个线程可以同时获取读锁,并发读;
  2. 独占模式(写锁):只有一个线程拿到写锁,写的时候所有读、写线程全部阻塞。
C++14 有 std::shared_timed_mutex,带超时;C++17 std::shared_mutex 去掉超时,性能更好。

核心接口

表格
接口 作用
lock() 独占上锁(写),阻塞;其他读 / 写全部等待
unlock() 释放独占锁
try_lock() 尝试独占上锁,拿不到直接返回 false
lock_shared() 共享上锁(读),多个线程可同时成功
unlock_shared() 释放共享锁
try_lock_shared() 尝试获取读锁
⚠️注意:
  • 写锁优先级:一旦有线程等待写锁,后续新来的lock_shared()会被阻挡,避免写线程永久饥饿;
  • 不能同一个线程重复调用lock_shared(),会死锁;
  • 不要混用:lock_shared()之后不能调用unlock(),必须配对unlock_shared()

RAII 包装(推荐,不要裸调用 lock)

C++ 提供两个 RAII 锁管理器配合 shared_mutex:
  1. std::shared_lockstd::shared_mutex → 获取共享读锁,构造调用lock_shared(),析构自动unlock_shared()
  2. std::unique_lockstd::shared_mutex → 获取独占写锁,构造调用lock(),析构自动unlock()
✅业务代码几乎永远用 RAII,不要手动lock/unlock,防止异常导致死锁。

完整示例代码

cpp
运行
#include <iostream>
#include <shared_mutex>
#include <thread>
#include <vector>

std::shared_mutex rw_mtx;
int shared_data = 0;

// 读线程:共享锁,多线程可并发执行
void reader(int id) {
    for (int i = 0; i < 3; ++i) {
        // 获取读锁,多个reader可以同时持有
        std::shared_lock<std::shared_mutex> lock(rw_mtx);
        std::cout << "reader" << id << ", data = " << shared_data << "\n";
        std::this_thread::sleep_for(std::chrono::milliseconds(10));
    }
}

// 写线程:独占锁,同一时间只能一个写
void writer() {
    for (int i = 0; i < 3; ++i) {
        // 获取写锁,排他,所有读/写阻塞
        std::unique_lock<std::shared_mutex> lock(rw_mtx);
        shared_data++;
        std::cout << "writer update data = " << shared_data << "\n";
        std::this_thread::sleep_for(std::chrono::milliseconds(50));
    }
}

int main() {
    std::vector<std::thread> threads;
    // 4个读线程
    for(int i=0;i<4;i++){
        threads.emplace_back(reader, i);
    }
    // 1个写线程
    threads.emplace_back(writer);

    for(auto& t : threads) t.join();
    return 0;
}

 

执行行为

  • 4 个 reader 可以同时打印,并发读;
  • writer 拿到锁瞬间,所有 reader 全部卡住;writer 执行完释放锁,读线程恢复并发。

使用场景 & 不适合场景

✅适合:读多写少
  • 配置表、缓存、全局只读数据,偶尔更新;
  • 大量线程查询,少量线程修改。
❌不适合:
  1. 读写频率差不多:读写锁有额外开销,不如普通std::mutex
  2. 写极频繁:写会不断阻塞读,性能反而差;
  3. 递归加锁:shared_mutex不支持递归,同一个线程重复 lock 会死锁;需要递归读写锁自己封装。

易错坑点

  1. 锁类型配对错误
cpp
运行
std::shared_lock<...> lk(rw_mtx);
rw_mtx.unlock(); // ❌错误,应该 unlock_shared(),RAII不会犯这个错

 

  1. 持有读锁的时候去做写操作
拿到shared_lock后,千万不要修改共享变量;此时其他线程也拿着读锁,会数据竞争。
  1. 死锁:读锁升级写锁(不支持
cpp
运行
std::shared_lock<std::shared_mutex> lk(rw_mtx);
// rw_mtx.lock(); // ❌ 已经持有共享锁,再申请独占锁直接死锁!

 

读锁不能直接升级写锁。正确做法:释放读锁,再获取写锁;或者先 try 升级,注意数据已经被别的线程修改。
  1. 写锁降级(C++17 支持)

    已经拿到unique_lock写锁,可以释放独占,转为共享读锁。

cpp
运行
std::unique_lock<std::shared_mutex> wlock(rw_mtx);
// 写操作...
wlock.unlock();
std::shared_lock<std::shared_mutex> rlock(rw_mtx);

 

和其他锁对比

表格
特性 适用场景
std::mutex 独占锁,简单快速 读写均衡,写多
std::shared_mutex 读写锁 C++17,无超时 读多写少
std::shared_timed_mutex 读写锁 + 超时 C++14 需要 try_lock 带超时

C++20 补充

C++20 对std::shared_mutex增加了try_lock_shared_for这类超时接口;同时支持 constexpr。

简单总结记忆

  • shared_mutex = 读写锁;
  • 读 → std::shared_lock
  • 写 → std::unique_lock
  • 读锁不能升级写锁,会直接死锁;
  • 读多写少才用,否则直接 mutex。
如果你需要,我可以给一段生产风格的封装示例(线程安全缓存类,基于 shared_mutex)。

发表回复