news 2026/7/19 17:37:03

C++ 递归锁(Recursive Lock)详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C++ 递归锁(Recursive Lock)详解

C++ 递归锁(Recursive Lock)详解

1. 什么是递归锁?

递归锁(Recursive Mutex)是指同一个线程可以多次对同一个互斥量加锁而不会导致死锁的互斥量。

C++ 标准库中对应的类是:

  • std::recursive_mutex:普通递归互斥量
  • std::recursive_timed_mutex:支持超时(timed)的递归互斥量

2. 为什么需要递归锁?(与std::mutex的对比)

特性std::mutexstd::recursive_mutex
同一线程重复加锁死锁(Undefined Behavior)允许,内部维护计数器
解锁次数要求加锁1次必须解锁1次必须解锁与加锁次数相同
性能更快稍慢(需要维护计数)
典型使用场景简单临界区递归调用、回调、同一线程多函数调用

经典场景

classFoo{std::mutex mtx;// 普通mutex会死锁// std::recursive_mutex mtx; // 改成这个就不会死锁public:voidfuncA(){std::lock_guard<std::mutex>lock(mtx);funcB();// 在已经持有锁的情况下再次调用 funcB}voidfuncB(){std::lock_guard<std::mutex>lock(mtx);// 同一线程再次加锁 → 死锁!// ...}};

如果把mtx换成std::recursive_mutex,上面代码就能正常工作。


3. 基本用法示例

#include<iostream>#include<thread>#include<mutex>#include<chrono>std::recursive_mutex rmtx;voidrecursive_func(intdepth){if(depth<=0)return;rmtx.lock();// 可以多次加锁std::cout<<"Depth "<<depth<<", thread id: "<<std::this_thread::get_id()<<std::endl;recursive_func(depth-1);rmtx.unlock();// 必须配对解锁}intmain(){std::threadt1(recursive_func,5);std::threadt2(recursive_func,3);t1.join();t2.join();return0;}

推荐写法(RAII)

voidbetter_func(){std::lock_guard<std::recursive_mutex>lock(rmtx);// 推荐// 或 std::unique_lock<std::recursive_mutex> lock(rmtx);// 即使抛出异常也会自动解锁}

4. 内部实现原理(简要)

std::recursive_mutex通常在内部维护两个关键信息:

  1. 当前拥有锁的线程 ID(owner_thread
  2. 锁的计数器(count
  • 第一次加锁:owner = this_threadcount = 1
  • 同一线程再次加锁:count++
  • 解锁:count--,当count == 0时才真正释放锁,其他线程才能获取

5.std::recursive_timed_mutex用法

std::recursive_timed_mutex rtm;voidtimed_example(){if(rtm.try_lock_for(std::chrono::milliseconds(100))){// 获取成功rtm.unlock();}std::unique_lock<std::recursive_timed_mutex>lock(rtm,std::defer_lock);if(lock.try_lock_for(std::chrono::seconds(1))){// ...}}

6. 注意事项和最佳实践

必须遵守的规则

  • 加锁次数必须等于解锁次数,否则其他线程永远拿不到锁(资源泄漏)。
  • 不要在不同线程间混用同一个递归锁(递归锁只对同一线程有特殊行为)。
  • 尽量减少递归锁的使用,它是“必要之恶”。

设计建议

  1. 优先使用非递归设计:把大函数拆分成“加锁部分”和“不加锁部分”。
  2. 使用std::lock_guard/std::unique_lock自动管理生命周期。
  3. 在类中封装时,可以考虑提供“已持锁”版本的内部函数:
classSafeClass{std::recursive_mutex mtx;void_internal_func(){// 内部已假定锁已持有// 不加锁的版本}public:voidpublic_func(){std::lock_guard<std::recursive_mutex>lock(mtx);_internal_func();}};

7. 性能与替代方案

  • 递归锁通常比普通std::mutex慢 10%~50%(取决于平台)。
  • 现代 C++ 中,更推荐细粒度锁无锁设计(lock-free)。
  • 对于需要“可重入”的场景,也可以考虑:
    • std::shared_mutex+ 读写分离
    • 线程本地存储(TLS)+ 普通 mutex
    • 重构代码避免递归加锁

总结

std::recursive_mutex是为同一线程可重入场景设计的工具,主要解决“已经在持有锁的情况下再次调用需要加同一把锁的函数”这个问题。使用时务必保证加锁和解锁严格配对,并优先考虑能否通过代码结构优化来避免使用它。

✅ 已优化代码示例结构

以下是更清晰、规范、现代的代码示例结构,推荐在实际项目中使用这种写法。

1. 推荐的完整优化示例(C++17/20)

#include<iostream>#include<thread>#include<mutex>#include<vector>#include<chrono>#include<string>classThreadSafeLogger{private:std::recursive_mutex mtx_;// 允许同一线程递归调用std::string prefix_;public:explicitThreadSafeLogger(std::string prefix="Log"):prefix_(std::move(prefix)){}// 公开接口:支持递归调用voidlog(conststd::string&message,intlevel=0){std::lock_guard<std::recursive_mutex>lock(mtx_);std::cout<<"["<<prefix_<<"] "<<std::string(level*2,' ')<<message<<std::endl;// 模拟递归调用(同一线程多次加锁)if(level<2){log(message+" (nested)",level+1);}}// 批量日志(演示多次加锁)voidlog_batch(conststd::vector<std::string>&messages){std::lock_guard<std::recursive_mutex>lock(mtx_);for(constauto&msg:messages){log(msg);// 再次进入 log(),触发递归锁}}// 非递归版本(内部使用,假设已持有锁)voidlog_internal(conststd::string&message){// 注意:调用此函数前必须已持有 mtx_!std::cout<<"[Internal] "<<message<<std::endl;}};intmain(){ThreadSafeLoggerlogger("Demo");// 示例1:单个线程递归调用std::cout<<"=== 单线程递归调用 ===\n";logger.log("开始任务");// 示例2:多线程测试std::cout<<"\n=== 多线程测试 ===\n";std::vector<std::thread>threads;for(inti=0;i<3;++i){threads.emplace_back([&logger,i](){std::string msg="线程 "+std::to_string(i)+" 的消息";logger.log(msg);logger.log_batch({"批处理1","批处理2"});});}for(auto&t:threads){t.join();}return0;}

2. 优化点说明

  1. 使用类封装:更贴近实际项目,而不是裸函数。
  2. RAII 完全替代:全部使用std::lock_guard,避免手动lock/unlock
  3. 区分公开接口与内部接口
    • log():供外部调用(加锁)
    • log_internal():内部使用(假定已加锁)
  4. 清晰的命名mtx_后缀表示成员变量。
  5. 移动语义:构造函数使用std::move
  6. 可扩展性强:易于后续添加try_locktimed等功能。

3. 进阶优化版本(推荐避免递归锁)

classBetterLogger{private:std::mutex mtx_;// 使用普通 mutex,更高效voidlog_internal(conststd::string&message,intlevel){std::cout<<"[Log] "<<std::string(level*2,' ')<<message<<std::endl;}public:voidlog(conststd::string&message,intlevel=0){std::lock_guard<std::mutex>lock(mtx_);log_internal(message,level);if(level<2){// 通过参数传递,避免递归加锁log_internal(message+" (nested)",level+1);}}};

结论:很多情况下,通过代码结构调整(把递归逻辑改为参数传递),可以完全避免使用std::recursive_mutex,获得更好的性能和可维护性。


你希望我继续优化哪个部分?

  • A. 更复杂的实际业务场景示例(如任务系统、回调系统)
  • B. 添加std::unique_lock+ 条件变量的完整示例
  • C. 性能对比测试代码
  • D. 如何安全地从递归锁迁移到普通 mutex 的技巧

✅ C++ 无锁并发编程(Lock-Free Concurrent Programming)详解

无锁编程是指**不使用互斥锁(mutex)**的情况下实现线程安全的技术。它是并发编程的高级阶段,能显著提升高并发场景下的性能和可扩展性。


1. 为什么需要无锁编程?

传统锁(std::mutex)存在以下问题:

  • 锁竞争:高线程数时性能急剧下降
  • 死锁:可能出现
  • 优先级反转:低优先级线程持有锁导致高优先级线程阻塞
  • 上下文切换:线程被阻塞时会发生昂贵的切换

无锁编程的优势

  • 无阻塞(或极少阻塞)
  • 更好的可扩展性(线程数增加时性能下降更平缓)
  • 避免死锁
  • 在某些场景下延迟更低

缺点

  • 代码复杂,容易出错
  • ABA 问题、内存回收困难
  • 调试困难
  • 并非所有场景都更快(低竞争时锁可能更快)

2. 核心概念

  • Lock-Free:至少有一个线程能持续前进(系统整体不会被永久阻塞)
  • Wait-Free:每个线程都能在有限步骤内完成(最强保证,通常最难实现)
  • Obstruction-Free:单个线程在无其他线程干扰时能完成

C++ 中主要通过std::atomic实现无锁编程。


3. 基础工具:std::atomic

#include<atomic>#include<cstdint>std::atomic<int>counter{0};std::atomic<bool>flag{false};std::atomic<uint64_t>shared_data{0};

常用操作

counter.store(42,std::memory_order_relaxed);// 写intval=counter.load(std::memory_order_acquire);// 读// 最重要操作:Compare-And-Swap (CAS)intexpected=10;intdesired=20;boolsuccess=counter.compare_exchange_weak(expected,desired,std::memory_order_release,std::memory_order_relaxed);

4. 内存顺序(Memory Order)—— 性能与正确性的关键

内存顺序含义性能适用场景
memory_order_seq_cst顺序一致(默认)最慢简单场景
memory_order_acquire读取时获取屏障读取共享数据
memory_order_release写入时释放屏障写入共享数据
memory_order_acq_relacquire + releaseRMW 操作
memory_order_relaxed无同步,仅原子性最快计数器、统计

5. 经典无锁数据结构示例

无锁递增计数器(最简单)
classLockFreeCounter{std::atomic<uint64_t>count_{0};public:voidincrement(){count_.fetch_add(1,std::memory_order_relaxed);}uint64_tget()const{returncount_.load(std::memory_order_acquire);}};
无锁栈(Lock-Free Stack)
template<typenameT>classLockFreeStack{structNode{T data;Node*next;Node(T val):data(std::move(val)),next(nullptr){}};std::atomic<Node*>head_{nullptr};public:voidpush(T value){Node*new_node=newNode(std::move(value));Node*old_head=head_.load(std::memory_order_relaxed);do{new_node->next=old_head;}while(!head_.compare_exchange_weak(old_head,new_node,std::memory_order_release,std::memory_order_relaxed));}boolpop(T&result){Node*old_head=head_.load(std::memory_order_acquire);while(old_head){if(head_.compare_exchange_weak(old_head,old_head->next,std::memory_order_acq_rel,std::memory_order_relaxed)){result=std::move(old_head->data);// TODO: 内存回收(hazard pointer 或 epoch-based)returntrue;}}returnfalse;}};

6. 常见难题与解决方案

  1. ABA 问题
    线程1 看到 A→B→A,误以为值没变。
    解决:使用带版本号的指针(tagged pointer)或std::atomic<std::shared_ptr>(C++20 较慢)。

  2. 内存回收
    不能直接delete,因为其他线程可能还在使用。
    常用方案

    • Hazard Pointers
    • Epoch-Based Reclamation (RCU 风格)
    • SMR (Safe Memory Reclamation)
  3. 无锁队列:Michael & Scott 队列(最经典)


7. C++20 后的新特性支持

  • std::atomic<std::shared_ptr<T>>(简化但性能一般)
  • std::atomic_ref<T>(C++20,对已有对象提供原子操作)
  • std::counting_semaphorestd::barrier

8. 实用建议

  • 低竞争场景→ 用std::mutex更简单高效
  • 高竞争、热点路径→ 考虑无锁或无等待算法
  • 先用std::atomic实现简单统计、标志位
  • 不要盲目追求无锁,先衡量性能瓶颈(用perf/ VTune)
  • 推荐库:
    • Boost.Lockfree
    • Folly(Facebook)
    • TBB(Intel Threading Building Blocks)
    • moodycamel::ConcurrentQueue(极受欢迎的无锁队列)

想深入学习哪个部分?

  1. 完整无锁队列实现 + ABA 解决
  2. Hazard Pointers 详细实现
  3. 性能对比测试(mutex vs atomic vs lockfree)
  4. 实际项目中的无锁模式(如内存池、日志系统)
  5. C++20/23 无锁新特性
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/19 17:35:04

Go语言_文件---文件操作

一、文件基础分类1. 设备文件&#xff08;标准输入 / 输出&#xff09;操作系统自带的默认打开设备文件&#xff0c;程序启动即可直接使用标准输出设备 stdout&#xff1a;屏幕fmt.Println() 底层封装 os.Stdout&#xff0c;向屏幕输出内容原生 API&#xff1a;os.Stdout.Write…

作者头像 李华
网站建设 2026/7/19 17:26:56

构建企业级配置系统:node-jsonc-parser在微服务架构中的应用

构建企业级配置系统&#xff1a;node-jsonc-parser在微服务架构中的应用 【免费下载链接】node-jsonc-parser Scanner and parser for JSON with comments. 项目地址: https://gitcode.com/gh_mirrors/no/node-jsonc-parser 在现代微服务架构中&#xff0c;配置管理是保…

作者头像 李华