news 2026/1/31 0:23:32

lock_guard和手动加锁下的try-catch 的作用域范围

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
lock_guard和手动加锁下的try-catch 的作用域范围

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

文章目录

      • 先明确核心前提
      • 一、两种写法的执行流程拆解
        • 1. incrementManual(try 包裹整个 for 循环)
        • 2. incrementGuard(try 包裹单次循环迭代)
      • 二、结果差异的核心原因
      • 三、关键补充:lock_guard 的安全性不受 try 位置影响
      • 总结

#include<iostream>#include<thread>#include<mutex>#include<vector>#include<stdexcept>#include<chrono>intg_count=0;std::mutex g_mutex;voidincrementManualGood(inttimes){try{for(inti=0;i<times;++i){g_mutex.lock();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();}}voidincrementManualBad(inttimes){for(inti=0;i<times;++i){try{g_mutex.lock();if(i==500){throwstd::runtime_error("手动加锁:临界区异常");}g_count++;g_mutex.unlock();// 正常路径解锁}catch(conststd::exception&e){std::cout<<"手动加锁线程捕获异常:"<<e.what()<<"\n";g_mutex.unlock();// 异常路径解锁}}}voidincrementGuardGood(inttimes){try{for(inti=0;i<times;++i){std::lock_guard<std::mutex>lock(g_mutex);if(i==500){throwstd::runtime_error("lock_guard:临界区异常");}g_count++;}}catch(conststd::exception&e){std::lock_guard<std::mutex>lock(g_mutex);// 加锁保证输出不乱码std::cout<<"lock_guard线程捕获异常:"<<e.what()<<"\n";}}voidincrementGuardBad(inttimes){for(inti=0;i<times;++i){try{std::lock_guard<std::mutex>lock(g_mutex);if(i==500){throwstd::runtime_error("lock_guard:临界区异常");}g_count++;}catch(conststd::exception&e){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;std::cout<<"===== 测试手动加锁(未手动解锁异常)=====\n";g_count=0;// 重置计数for(inti=0;i<thread_num;++i){threads.emplace_back(incrementGuardBad,times_per_thread);}for(auto&t:threads){if(t.joinable()){t.join();}}std::cout<<"手动加锁最终count值:"<<g_count<<"\n";// 这行永远执行不到return0;}

上面代码中,good结尾,结果为预想值,bad结尾非预想值,下面是解释

核心差异并非lock_guard或手动加锁的安全性问题,而是try-catch 的作用域范围改变了「异常对循环生命周期的影响」,最终导致计数结果不同。我会拆解两种写法的执行流程,清晰说明结果差异的原因。

先明确核心前提

我们的预设场景:5个线程,每个线程循环1000次,i=500时抛异常;手动版(try包整个循环)结果≈2500(5线程×500次),而lock_guard版(try包单次迭代)结果≠2500(实际远大于,你感知的“小于”是描述偏差,本质是和预期不一致)。


一、两种写法的执行流程拆解

1. incrementManual(try 包裹整个 for 循环)
voidincrementManual(inttimes){try{// try 覆盖【全部1000次循环】for(inti=0;i<times;++i){// times=1000g_mutex.lock();if(i==500)throw...;// 第501次循环触发异常g_count++;g_mutex.unlock();}}catch(...){g_mutex.unlock();// 手动补解锁}}

单个线程执行步骤

  • i=0 到 i=499:共500次循环,每次正常lock() → count++ → unlock(),累计计数500次;
  • i=500:lock()后抛异常 → 直接跳出整个 for 循环,进入外层 catch → 手动unlock()
  • 异常处理后,循环永久终止,i=501 到 999 都不会执行;
  • 最终:单个线程计数500次 → 5个线程总计 5×500=2500(和预期一致)。
2. incrementGuard(try 包裹单次循环迭代)
voidincrementGuard(inttimes){for(inti=0;i<times;++i){// times=1000// try 仅覆盖【单次循环迭代】try{std::lock_guard<std::mutex>lock(g_mutex);if(i==500)throw...;g_count++;}catch(...){std::cout<<"捕获异常:"<<e.what()<<"\n";}}}

单个线程执行步骤

  • i=0 到 i=499:共500次循环,每次正常lock_guard加锁 → count++ → 解锁,累计计数500次;
  • i=500:lock_guard加锁后抛异常 →lock_guard析构自动解锁 → 异常被当前迭代的 catch捕获(仅终止本次迭代);
  • 关键:catch 处理完后,外层 for 循环不会终止,程序回到循环头执行 i=501;
  • i=501 到 999:共499次循环,每次正常计数,累计计数499次;
  • 最终:单个线程计数 500+499=999次 → 5个线程总计 5×999=4995(远大于预期的2500)。

二、结果差异的核心原因

维度incrementManual(try包整个循环)incrementGuard(try包单次迭代)
try-catch 作用域覆盖全部1000次循环仅覆盖单次循环迭代
异常传播行为异常跳出整个 for 循环,线程终止计数异常被本地catch捕获,仅终止当前迭代,循环继续
单个线程计数次数500次(0-499)999次(0-499 + 501-999)
5线程总计2500(符合预期)4995(偏离预期)

你感知的“结果小于预设值”大概率是描述偏差,本质是「异常是否终止整个循环」导致计数和预期(2500)不一致——而非lock_guard本身有缺陷。


三、关键补充:lock_guard 的安全性不受 try 位置影响

无论 try-catch 包“整个循环”还是“单次迭代”,lock_guard都能保证:异常发生时自动解锁(RAII 机制),这是手动加锁永远比不了的核心优势。

如果想让lock_guard版也输出 2500(和手动版结果一致),只需把 try-catch 移到循环外层(和手动版对齐):

voidincrementSafeConsistent(inttimes){try{// try 包裹整个循环,异常终止循环for(inti=0;i<times;++i){std::lock_guard<std::mutex>lock(g_mutex);if(i==500)throwstd::runtime_error("意外异常");g_count++;}}catch(conststd::exception&e){std::cout<<"捕获异常:"<<e.what()<<"\n";}}

修改后:

  • 单个线程计数500次 → 5线程总计2500(和手动版结果一致);
  • 同时保留lock_guard自动解锁的优势,无需手动处理解锁。

总结

  1. 核心差异:try-catch 的作用域范围决定了异常是否终止整个循环——手动版终止循环,lock_guard版仅终止单次迭代,最终计数结果不同;
  2. 本质原因:结果差异是「异常对循环生命周期的影响」导致,而非锁的安全性问题;
  3. 最佳实践lock_guard搭配“try包裹整个循环”是最优解——既保证异常时自动解锁,又能让计数结果符合预期,且代码简洁、无手动解锁的风险。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/1/29 10:24:31

2026高职大数据专业应届生就业方向与实战指南

大数据行业持续高速发展&#xff0c;2026年高职大数据专业应届生可选择的就业方向广泛&#xff0c;涵盖数据分析、数据工程、人工智能等多个领域。以下从就业方向、技能要求、薪资水平及实战路径展开分析&#xff0c;并附CDA数据分析师证书的详细说明。主要就业方向与岗位需求岗…

作者头像 李华
网站建设 2026/1/29 10:24:12

Spring Boot后端服务的AI压力测试实战

一、引言&#xff1a;AI驱动的压力测试变革 在当今高并发互联网应用中&#xff0c;Spring Boot后端服务的稳定性至关重要。传统压力测试依赖手动编写脚本&#xff0c;耗时且易出错&#xff0c;而AI技术的融入正彻底改变这一局面。通过机器学习算法和自然语言处理&#xff0c;A…

作者头像 李华
网站建设 2026/1/29 10:20:23

数据魔法师养成记:书匠策AI如何让你的教育论文“数据力”爆表

在学术江湖里&#xff0c;数据是论文的“骨骼”&#xff0c;逻辑是“肌肉”&#xff0c;而分析方法则是让论文“活”起来的“魔法咒语”。但现实往往很骨感&#xff1a;教育学研究者常因找不到被试者而抓狂&#xff0c;心理学学生被SPSS代码折磨到脱发&#xff0c;就连资深学者…

作者头像 李华
网站建设 2026/1/29 10:20:16

教育论文的“数据炼金师”:书匠策AI如何将数字转化为学术金矿

在教育研究领域&#xff0c;数据是支撑论点的“骨骼”&#xff0c;但如何让杂乱无章的数据“开口说话”&#xff0c;却让无数研究者头疼。传统数据分析工具门槛高、操作复杂&#xff0c;而学术规范又对数据呈现的严谨性要求极高。今天&#xff0c;我们揭秘一款专为教育研究者打…

作者头像 李华
网站建设 2026/1/29 10:18:53

​2026年GEO公司推荐:构建品牌AI知识资产的核心伙伴

2026年&#xff0c;中国生成式AI搜索&#xff08;GEO&#xff09;市场迎来爆发式增长期&#xff0c;市场规模突破480亿元&#xff0c;年复合增长率高达68%。在这一行业热潮下&#xff0c;如何科学筛选专业GEO服务伙伴&#xff0c;已成为企业数字战略布局的关键课题。本榜单基于…

作者头像 李华
网站建设 2026/1/30 16:16:06

软件工程:(一)重塑软件思维与线性表精要

目录 一、软件工程导论&#xff08;宏观篇&#xff09; 1. 软件危机&#xff1a;为什么程序员总在加班&#xff1f; 2. 软件生命周期&#xff1a;软件的一生 二、数据结构——线性表&#xff08;微观篇&#xff09; 1. 顺序表 (Sequential List) 2. 链表 (Linked List) 知…

作者头像 李华