std::recursive_mutex是C++标准库提供的递归互斥锁,属于<mutex>头文件中的组件,它的核心特性是允许同一个线程对同一个互斥锁对象进行多次加锁操作,而不会因为重复获取锁导致死锁。

std::recursive_mutex的核心作用
普通互斥锁std::mutex不允许同一个线程重复加锁,如果同一个线程已经持有锁,再次尝试加锁会直接阻塞或者返回错误,这会导致递归函数调用或者嵌套加锁的场景出现死锁。而std::recursive_mutex就是为了解决这类问题设计的,它的主要作用包括:
- 支持递归函数的线程安全实现,递归函数在执行过程中会多次进入函数体,每次进入都需要获取锁保护共享资源,普通互斥锁无法满足这个需求。
- 支持嵌套加锁场景,比如一个函数获取锁之后调用另一个也需要获取同一个锁的函数,普通互斥锁会导致死锁,递归互斥锁可以正常处理这种情况。
- 在保证线程安全的前提下,简化复杂调用逻辑下的锁管理,避免开发者手动处理复杂的锁状态判断。
使用规则与注意事项
使用std::recursive_mutex需要遵循几个重要规则,否则还是会出现问题:
- 同一个线程加锁多少次,就必须解锁多少次,否则其他线程永远无法获取这个锁。比如线程A加了3次锁,那么必须调用3次unlock,锁才会真正释放。
- 不同线程之间的加锁操作仍然是互斥的,只有同一个线程的重复加锁才会被允许,其他线程尝试加锁时如果锁已经被持有,还是会进入阻塞状态。
- 不要滥用递归互斥锁,因为维护递归计数会带来一定的性能开销,如果不需要重复加锁的场景,优先使用普通std::mutex。
代码示例
下面通过一个递归函数的例子展示std::recursive_mutex的用法,这个例子中递归函数会多次进入,每次都需要获取锁来保护共享计数器:
#include <iostream>
#include <mutex>
#include <thread>
// 定义递归互斥锁
std::recursive_mutex rec_mtx;
// 共享计数器
int shared_counter = 0;
// 递归函数,会重复获取同一个锁
void recursive_func(int depth) {
// 第一次加锁
rec_mtx.lock();
std::cout << "线程 " << std::this_thread::get_id() << " 进入递归,深度: " << depth << std::endl;
shared_counter++;
if (depth > 0) {
// 递归调用,再次尝试加锁同一个递归互斥锁,不会死锁
recursive_func(depth - 1);
}
std::cout << "线程 " << std::this_thread::get_id() << " 离开递归,深度: " << depth << std::endl;
// 对应一次加锁的解锁操作
rec_mtx.unlock();
}
int main() {
// 创建两个线程执行递归函数
std::thread t1(recursive_func, 2);
std::thread t2(recursive_func, 2);
t1.join();
t2.join();
std::cout << "最终共享计数器的值: " << shared_counter << std::endl;
return 0;
}
上面的代码中,recursive_func是递归函数,每次进入都会调用lock获取rec_mtx锁,递归调用时再次获取同一个锁不会死锁,因为rec_mtx是std::recursive_mutex类型。每次lock都对应一次unlock,最终两个线程执行完成后,shared_counter的值会是4,符合预期。
与普通std::mutex的对比
两者的核心区别可以通过以下表格清晰展示:
| 特性 | std::mutex | std::recursive_mutex |
|---|---|---|
| 同一线程重复加锁 | 不允许,会导致死锁或错误 | 允许,需要记录加锁次数 |
| 加锁解锁次数要求 | 加锁和解锁次数必须相等,且同一线程只能解锁自己加的锁 | 同一线程加锁次数和解锁次数必须完全相等,否则锁无法释放 |
| 性能开销 | 较小,无额外计数逻辑 | 稍大,需要维护线程ID和加锁计数 |
| 适用场景 | 无嵌套、无递归的普通加锁场景 | 递归调用、嵌套加锁的场景 |
适用场景总结
std::recursive_mutex适合以下场景使用:
- 函数存在递归调用,且递归过程中需要反复获取同一个锁保护共享资源。
- 多个函数之间存在嵌套调用关系,且这些函数都需要获取同一个锁。
- 无法确定调用链是否会重复获取同一个锁的复杂业务逻辑,且可以接受一定的性能开销。
如果可以提前梳理清楚调用逻辑,避免重复加锁,还是优先选择普通std::mutex,这样能获得更好的性能。另外也可以使用std::unique_lock配合std::recursive_mutex,利用RAII机制自动管理加锁解锁,避免忘记解锁的问题,比如下面的写法:
#include <mutex>
#include <unique_lock>
std::recursive_mutex rec_mtx;
void safe_func() {
// 使用unique_lock自动管理锁,离开作用域自动解锁
std::unique_lock<std::recursive_mutex> lock(rec_mtx);
// 业务逻辑
// 即使这里再次调用需要加锁的函数,也不会死锁
// 离开作用域时,unique_lock会自动调用unlock,次数和加锁次数匹配
}
std::recursive_mutexC++多线程互斥锁修改时间:2026-07-23 19:09:28