2026/4/3 9:05:25
网站建设
项目流程
投票网站开发,wordpress素材下载站,北京外贸网站建设公司,久久建筑网和恒智天成那个软件好提示#xff1a;文章写完后#xff0c;目录可以自动生成#xff0c;如何生成可参考右边的帮助文档 文章目录 一、为什么解开注释后能正常运行#xff1f;二、手动解锁的隐藏风险#xff08;为什么不推荐#xff09;1. 风险1#xff1a;多个退出点导致解锁遗漏2. 风险2文章写完后目录可以自动生成如何生成可参考右边的帮助文档文章目录一、为什么解开注释后能正常运行二、手动解锁的隐藏风险为什么不推荐1. 风险1多个退出点导致解锁遗漏2. 风险2lock 后抛异常不在 try 块内3. 风险3catch 块内再抛异常三、lock_guard 为什么是“真正可行”的方案总结#includeiostream#includethread#includemutex#includevector#includestdexcept#includechronointg_count0;std::mutex g_mutex;// 手动加锁解锁 voidincrementManual(inttimes){try{for(inti0;itimes;i){// 手动加锁g_mutex.lock();// 模拟临界区异常第500次循环触发if(i500){std::cout手动加锁线程触发异常unlock() 将无法执行\n;throwstd::runtime_error(手动加锁临界区异常);}g_count;// 手动解锁⚠️ 异常会跳过这行锁永远无法释放g_mutex.unlock();}}catch(conststd::exceptione){std::cout手动加锁线程捕获异常e.what()\n;// 【修复提示】手动加锁必须在这里手动解锁否则死锁但新手极易遗漏g_mutex.unlock();// 若注释掉这行必然死锁解开则能解锁但代码易出错//但是测试发现结果是对的}}intmain(){constintthread_num5;constinttimes_per_thread1000;std::vectorstd::threadthreads;std::cout 测试手动加锁未手动解锁异常\n;g_count0;// 重置计数for(inti0;ithread_num;i){threads.emplace_back(incrementManual,times_per_thread);}for(autot: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(inti0;itimes;i){g_mutex.lock();// 新增提前退出分支新手易忽略if(i300){std::cout提前return跳过unlock\n;return;// 直接return既跳过正常unlock也进不去catch}if(i500){throwstd::runtime_error(临界区异常);}g_count;g_mutex.unlock();}}catch(conststd::exceptione){g_mutex.unlock();// 仅覆盖异常场景覆盖不了return场景std::cout捕获异常e.what()\n;}}现象当线程执行到i300时return跳过了所有解锁逻辑锁被永久占用程序卡死lock_guard 对比哪怕returnlock_guard也会在离开作用域时自动析构解锁完全无风险。2. 风险2lock 后抛异常不在 try 块内如果lock后、try块前抛异常比如参数非法catch块根本捕获不到解锁代码执行不到voidincrementManualRisk2(inttimes){for(inti0;itimes;i){g_mutex.lock();// 异常抛在try块外catch捕获不到if(times0){throwstd::runtime_error(参数非法);// 解锁代码永远执行不到}try{if(i500){throwstd::runtime_error(临界区异常);}g_count;g_mutex.unlock();}catch(conststd::exceptione){g_mutex.unlock();// 仅能捕获try块内的异常std::cout捕获异常e.what()\n;}}}现象若传入times-1异常抛在try块外catch无法处理解锁代码执行不到直接死锁这里AI说错了传-1连循环都进入不了但类似的如果进入这种未捕获抛出会崩溃lock_guard 对比只要lock_guard构造完成无论异常在哪抛析构都会解锁哪怕构造前抛异常比如参数非法lock_guard没构造也不会加锁无风险。3. 风险3catch 块内再抛异常如果catch块里处理逻辑又抛异常手动解锁代码会被跳过voidincrementManualRisk3(inttimes){try{for(inti0;itimes;i){g_mutex.lock();if(i500){throwstd::runtime_error(临界区异常);}g_count;g_mutex.unlock();}}catch(conststd::exceptione){// 处理异常时再抛新异常跳过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 工程中处理互斥锁的标准最佳实践。总结解开catch里的unlock能运行不是巧合它补上了异常场景的解锁实现了“加锁-解锁”成对但仅适配当前极简场景手动解锁不可行依赖开发者覆盖所有退出/异常路径极易因代码修改、逻辑遗漏导致死锁lock_guard是工程上可靠的方案RAII 机制自动保证解锁无需关注异常/退出路径代码更简洁、更健壮。简单说手动解锁是“靠人保证不出错”lock_guard是“靠语言机制保证不出错”——而工程开发中机制永远比人的细心更可靠。