news 2026/4/16 13:57:59

手动加锁解锁版本catch里解锁结果正确分析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
手动加锁解锁版本catch里解锁结果正确分析

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

      • 一、为什么解开注释后能正常运行?
      • 二、手动解锁的隐藏风险(为什么不推荐)
        • 1. 风险1:多个退出点导致解锁遗漏
        • 2. 风险2:lock 后抛异常不在 try 块内
        • 3. 风险3:catch 块内再抛异常
      • 三、lock_guard 为什么是“真正可行”的方案?
      • 总结

#include<iostream>#include<thread>#include<mutex>#include<vector>#include<stdexcept>#include<chrono>intg_count=0;std::mutex g_mutex;// ========== 手动加锁解锁 ==========voidincrementManual(inttimes){try{for(inti=0;i<times;++i){// 手动加锁g_mutex.lock();// 模拟临界区异常(第500次循环触发)if(i==500){std::cout<<"手动加锁线程:触发异常,unlock() 将无法执行!\n";throwstd::runtime_error("手动加锁:临界区异常");}g_count++;// 手动解锁(⚠️ 异常会跳过这行,锁永远无法释放)g_mutex.unlock();}}catch(conststd::exception&e){std::cout<<"手动加锁线程捕获异常:"<<e.what()<<"\n";// 【修复提示】手动加锁必须在这里手动解锁,否则死锁,但新手极易遗漏g_mutex.unlock();// 若注释掉这行,必然死锁;解开则能解锁,但代码易出错//但是测试发现结果是对的}}intmain(){constintthread_num=5;constinttimes_per_thread=1000;std::vector<std::thread>threads;std::cout<<"===== 测试手动加锁(未手动解锁异常)=====\n";g_count=0;// 重置计数for(inti=0;i<thread_num;++i){threads.emplace_back(incrementManual,times_per_thread);}for(auto&t:threads){if(t.joinable()){t.join();// 程序会卡在这一行,无法继续}}std::cout<<"手动加锁最终count值:"<<g_count<<"\n";// 这行永远执行不到return0;}

你解开catch块里的g_mutex.unlock();注释后,代码能正常执行且结果正确并不是巧合,但这种「手动在 catch 里解锁」的做法仅在这个极简场景下可行,在工程实践中是脆弱且不推荐的——它依赖开发者对所有异常/退出路径的精准把控,稍有不慎就会出问题。

一、为什么解开注释后能正常运行?

核心逻辑是:
你在try块里调用了g_mutex.lock()加锁,抛出异常后进入catch块,此时手动调用g_mutex.unlock()补上了解锁操作,让互斥锁被释放。
这就实现了「加锁-解锁」的成对执行,因此不会死锁,其他线程能正常竞争锁,最终g_count也能累加至预期的 2500。

但这只是「刚好覆盖当前场景」,并非通用的安全做法。

二、手动解锁的隐藏风险(为什么不推荐)

下面用具体例子展示:哪怕只是轻微修改代码,手动解锁就会失效,而lock_guard始终安全。

1. 风险1:多个退出点导致解锁遗漏

如果函数里有提前 return多个异常分支,很容易漏掉某个路径的解锁:

voidincrementManualRisk1(inttimes){try{for(inti=0;i<times;++i){g_mutex.lock();// 新增:提前退出分支(新手易忽略)if(i==300){std::cout<<"提前return,跳过unlock!\n";return;// 直接return,既跳过正常unlock,也进不去catch}if(i==500){throwstd::runtime_error("临界区异常");}g_count++;g_mutex.unlock();}}catch(conststd::exception&e){g_mutex.unlock();// 仅覆盖异常场景,覆盖不了return场景std::cout<<"捕获异常:"<<e.what()<<"\n";}}
  • 现象:当线程执行到i=300return,跳过了所有解锁逻辑,锁被永久占用,程序卡死;
  • lock_guard 对比:哪怕returnlock_guard也会在离开作用域时自动析构解锁,完全无风险。
2. 风险2:lock 后抛异常不在 try 块内

如果lock后、try块前抛异常(比如参数非法),catch块根本捕获不到,解锁代码执行不到:

voidincrementManualRisk2(inttimes){for(inti=0;i<times;++i){g_mutex.lock();// 异常抛在try块外,catch捕获不到if(times<0){throwstd::runtime_error("参数非法");// 解锁代码永远执行不到}try{if(i==500){throwstd::runtime_error("临界区异常");}g_count++;g_mutex.unlock();}catch(conststd::exception&e){g_mutex.unlock();// 仅能捕获try块内的异常std::cout<<"捕获异常:"<<e.what()<<"\n";}}}
  • 现象:若传入times=-1,异常抛在try块外,catch无法处理,解锁代码执行不到,直接死锁;
  • 这里AI说错了,传-1连循环都进入不了,但类似的如果进入这种未捕获抛出会崩溃
  • lock_guard 对比:只要lock_guard构造完成,无论异常在哪抛,析构都会解锁;哪怕构造前抛异常(比如参数非法),lock_guard没构造,也不会加锁,无风险。
3. 风险3:catch 块内再抛异常

如果catch块里处理逻辑又抛异常,手动解锁代码会被跳过:

voidincrementManualRisk3(inttimes){try{for(inti=0;i<times;++i){g_mutex.lock();if(i==500){throwstd::runtime_error("临界区异常");}g_count++;g_mutex.unlock();}}catch(conststd::exception&e){// 处理异常时再抛新异常,跳过unlockthrowstd::runtime_error("处理异常时出错:"+std::string(e.what()));g_mutex.unlock();// 这行永远执行不到}}
  • 现象:新异常抛出后,unlock被跳过,锁无法释放,程序卡死;
  • lock_guard 对比:哪怕catch块抛新异常,lock_guard早已在try块结束时析构解锁,无影响。

三、lock_guard 为什么是“真正可行”的方案?

std::lock_guard的核心是RAII(资源获取即初始化),它把「加锁-解锁」绑定到对象的「构造-析构」:

  • 构造lock_guard对象 → 自动调用lock()
  • 无论正常退出作用域(比如循环结束)、异常退出(抛异常)、提前 return,只要对象离开作用域,析构函数就会自动调用unlock()

这种“自动绑定”的方式,从根本上避免了“开发者手动把控所有路径”的人为失误,是 C++ 工程中处理互斥锁的标准最佳实践

总结

  1. 解开catch里的unlock能运行不是巧合:它补上了异常场景的解锁,实现了“加锁-解锁”成对,但仅适配当前极简场景;
  2. 手动解锁不可行:依赖开发者覆盖所有退出/异常路径,极易因代码修改、逻辑遗漏导致死锁;
  3. lock_guard工程上可靠的方案:RAII 机制自动保证解锁,无需关注异常/退出路径,代码更简洁、更健壮。

简单说:手动解锁是“靠人保证不出错”,lock_guard是“靠语言机制保证不出错”——而工程开发中,机制永远比人的细心更可靠。

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

一次买断的物联网平台

物联网平台 - Thinglinks-iot ## &#x1f31f; 项目简介 一个功能完备、高可扩展的物联网平台&#xff0c;用最少的代码接入设备&#xff0c;基于Ruoyi-vue框架&#xff0c;支持Mysql和pgsql双版本&#xff0c;集成mybatis-plus&#xff0c;集成TCP、MQTT、UDP、CoAP、HTTP、…

作者头像 李华
网站建设 2026/4/16 9:13:55

亲测好用8个AI论文软件,研究生高效写作必备!

亲测好用8个AI论文软件&#xff0c;研究生高效写作必备&#xff01; AI 工具让论文写作不再“难上加难” 在研究生阶段&#xff0c;论文写作是一项不可避免的任务&#xff0c;而如何高效、高质量地完成它&#xff0c;成为了许多学生关注的焦点。随着 AI 技术的不断发展&#xf…

作者头像 李华
网站建设 2026/4/16 9:13:54

‌AI红娘测试:为什么匹配的恋人总在隔壁城市?——一名软件测试工程师的系统性归因分析

地理匹配偏差并非偶然&#xff0c;而是推荐系统在数据采集、特征工程与测试验证三重环节中系统性失效的必然结果‌ 在AI红娘类应用中&#xff0c;“匹配对象总在隔壁城市”这一现象&#xff0c;本质是‌推荐系统在未充分建模用户真实行为分布、未验证地理特征的因果性、未设计…

作者头像 李华
网站建设 2026/4/16 11:04:22

AI 驱动的 JSON 可视化渲染引擎(vercel-labs 开源 JSON-Render)

1. 引言:AI 驱动的界面生成新时代 1.1 从自然语言到可视化界面的演进 在人工智能技术飞速发展的今天,AI 已经能够理解和生成自然语言,这使得它具备了将人类语言描述转化为结构化数据的能力。JSON-Render 正是利用这一特性,将 AI 生成的自然语言描述转化为 JSON 结构,进而…

作者头像 李华
网站建设 2026/4/16 11:13:34

《跨城工作成常态,社保如何“随身带”?希创人事给出答案》

这两年&#xff0c;一个明显的变化正在发生&#xff1a;工作在北京&#xff0c;生活在天津&#xff1b;上一份工作在上海&#xff0c;下一站去了深圳&#xff1b;刚离职&#xff0c;还没想好下一步&#xff0c;但房子、孩子、落户、医疗却一样都不能断。跨城流动&#xff0c;正…

作者头像 李华