news 2026/6/24 13:20:39

两版本锁抛出异常测试

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
两版本锁抛出异常测试

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

文章目录

      • 核心思路
      • 完整对比示例代码
      • 关键运行结果与解释
        • 1. 手动加锁版本(测试1)的问题
        • 2. lock_guard 版本(测试2)的正常表现
      • 总结

你想测试手动调用std::mutexlock()/unlock()方法时,临界区抛出异常并捕获的场景,对比std::lock_guard看看手动加锁是否存在缺陷(比如g_count无法累加至预设值、甚至死锁),我会为你编写手动加锁 vs lock_guard的对比示例,并详细解释核心问题。

核心思路

手动加锁的关键缺陷:如果临界区抛出异常,unlock()语句若写在异常之后,会因异常中断执行而永远无法解锁,导致互斥锁被永久占用(死锁),其他线程阻塞,最终g_count既达不到预设值,程序还可能卡死;而lock_guard基于 RAII 机制,即使异常也会触发析构函数自动解锁,不会出现这类问题。


完整对比示例代码

#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(); // 若注释掉这行,必然死锁;解开则能解锁,但代码易出错}}// ========== lock_guard 加锁(安全版本) ==========voidincrementGuard(inttimes){try{for(inti=0;i<times;++i){// RAII 加锁:构造时lock,析构时自动unlock(无论是否异常)std::lock_guard<std::mutex>lock(g_mutex);// 同样模拟临界区异常if(i==500){std::cout<<"lock_guard线程:触发异常,析构会自动解锁!\n";throwstd::runtime_error("lock_guard:临界区异常");}g_count++;}}catch(conststd::exception&e){// 仅捕获异常,无需手动解锁(lock_guard已自动处理)std::lock_guard<std::mutex>lock(g_mutex);// 加锁保证输出不乱码std::cout<<"lock_guard线程捕获异常:"<<e.what()<<"\n";}}intmain(){constintthread_num=5;constinttimes_per_thread=1000;std::vector<std::thread>threads;// ========== 测试1:手动加锁(会死锁/计数异常) ==========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";// 这行永远执行不到// ========== 测试2:lock_guard 加锁(正常执行) ==========// 【注意】先注释掉测试1的代码,再运行测试2,否则测试1卡死无法执行到这里// std::cout << "\n===== 测试lock_guard加锁(安全)=====\n";// g_count = 0;// threads.clear();// for (int i = 0; i < thread_num; ++i) {// threads.emplace_back(incrementGuard, times_per_thread);// }// for (auto& t : threads) {// if (t.joinable()) {// t.join();// }// }// std::cout << "lock_guard最终count值:" << g_count << "\n"; // 输出 2500return0;}

关键运行结果与解释

1. 手动加锁版本(测试1)的问题
  • 现象:程序运行后会输出「手动加锁线程:触发异常,unlock() 将无法执行!」,然后卡死在t.join()g_count的最终值永远打印不出来;
  • 核心原因
    • 线程执行到i=500时抛出异常,跳过了后续的g_mutex.unlock()
    • 互斥锁被该线程永久持有,其他线程调用g_mutex.lock()时会阻塞(拿不到锁),最终所有线程卡死,g_count通常远低于 2500(甚至只有几百);
    • 即使在catch块中手动加g_mutex.unlock()(解开代码中注释),也能解锁,但新手极易遗漏这行代码,且代码逻辑会变得繁琐(每个异常分支都要考虑解锁)。
2. lock_guard 版本(测试2)的正常表现
  • 现象:程序输出「lock_guard线程:触发异常,析构会自动解锁!」,无卡死,最终g_count输出2500(5个线程×每个执行500次);
  • 核心原因
    • std::lock_guard是 RAII 封装:构造时调用lock()无论正常执行还是异常退出作用域,析构函数都会自动调用unlock()
    • 即使抛出异常,锁也能被释放,其他线程可正常竞争锁,g_count能累加至预期值(每个线程执行到500次抛异常,总计 5×500=2500)。

总结

  1. 手动加锁的核心缺陷:异常会跳过unlock()语句,导致锁永久占用(死锁),g_count累加异常;即使手动在catch中解锁,代码也易出错、可读性差;
  2. lock_guard 的核心优势:RAII 机制保证「加锁-解锁」成对出现,无论临界区正常执行还是抛出异常,都能自动释放锁,避免死锁,计数结果符合预期;
  3. 实战建议:除非有特殊需求(如手动控制解锁时机),否则优先使用std::lock_guard(或std::unique_lock),避免手动调用lock()/unlock()

如果想测试「手动加锁但在catch中解锁」的修复版本,只需解开incrementManualcatch块里的g_mutex.unlock();注释,此时程序不会死锁,g_count也能输出 2500,但代码复杂度远高于lock_guard版本。

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

吐血推荐!继续教育10款AI论文软件深度测评

吐血推荐&#xff01;继续教育10款AI论文软件深度测评 2026年继续教育AI论文工具测评&#xff1a;为何需要这份榜单&#xff1f; 在继续教育领域&#xff0c;论文写作已成为许多学员提升学历、拓展职业发展的关键环节。然而&#xff0c;面对繁重的工作任务与有限的写作时间&a…

作者头像 李华
网站建设 2026/6/22 12:01:44

初始C++: C++入门以及 类和对象初识

编译环境为VS2022 文章目录 前言一.初识C1.第一个C程序的编写2.命名空间的了解3.C的输入输出4.C的引用一.引用是什么&#xff1f;二.引用的特性 和 const引用的了解三.指针和引用的关系 二.类和对象初识1.类的定义2.类的访问限定符3.this指针4.类的默认成员函数一.构造函数二.析…

作者头像 李华
网站建设 2026/6/15 18:47:52

基于SpringBoot的拼装模型销售管理系统的设计与实现

文章目录 详细视频演示项目介绍技术介绍功能介绍核心代码系统效果图源码获取 详细视频演示 文章底部名片&#xff0c;获取项目的完整演示视频&#xff0c;免费解答技术疑问 项目介绍 基于SpringBoot的拼装模型销售管理系统是专为模型零售行业设计的数字化管理工具&#xff0c…

作者头像 李华
网站建设 2026/6/21 6:31:07

深入解析 PHP OPcache—从核心原理到高级配置

摘要 本报告旨在全面、深入地探讨 PHP 的核心性能组件——OPcache。报告将从 OPcache 的基本定义和历史演进出发&#xff0c;详细阐述其在 PHP 脚本执行生命周期中的关键作用及其内部工作机制&#xff0c;包括字节码缓存、共享内存利用和缓存验证策略。随后&#xff0c;报告将…

作者头像 李华
网站建设 2026/6/21 6:31:10

基于Gerchberg-Saxton(GS)算法实现衍射光学元件(DOE)设计

一、GS算法核心原理 1. 基本迭代流程 % 初始化参数 N 512; % 采样点数 lambda 632.8e-9;% 波长 k 2*pi/lambda; % 波数 d 1e-3; % 传播距离% 输入输出振幅约束 A_input rand(N,N); % 随机输入振幅 A_target ones(N,N); % 目标振幅&#xff08;平顶光…

作者头像 李华
网站建设 2026/6/20 21:08:59

【毕业设计】基于springboot的文物知识科普“江西文物时讯”微信小程序(源码+文档+远程调试,全bao定制等)

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

作者头像 李华