核心内容摘要
照着用就行:8个降AIGC工具测评,专科生降AI率必备攻略
提示文章写完后目录可以自动生成如何生成可参考右边的帮助文档文章目录核心思路完整对比示例代码关键运行结果与解释
手动加锁版本测试1的问题
lock_guard 版本测试2的正常表现
总结你想测试手动调用std::mutex的lock()/unlock()方法时临界区抛出异常并捕获的场景对比std::lock_guard看看手动加锁是否存在缺陷比如g_count无法累加至预设值、甚至死锁我会为你编写手动加锁 vs lock_guard的对比示例并详细解释核心问题。
核心思路手动加锁的关键缺陷如果临界区抛出异常unlock()语句若写在异常之后会因异常中断执行而永远无法解锁导致互斥锁被永久占用死锁其他线程阻塞最终g_count既达不到预设值程序还可能卡死而lock_guard基于 RAII 机制即使异常也会触发析构函数自动解锁不会出现这类问题。
完整对比示例代码#includeiostream#includethread#includemutex#includevector#includestdexcept#includechronointg_count0;std::mutex g_mutex;// 手动加锁解锁有缺陷版本 voidincrementManual(inttimes){try{for(inti0;itimes;i){// 手动加锁g_mutex.lock();// 模拟临界区异常第500次循环触发if(i
{std::cout手动加锁线程触发异常unlock() 将无法执行\n;throwstd::runtime_error(手动加锁临界区异常);}g_count;// 手动解锁⚠️ 异常会跳过这行锁永远无法释放g_mutex.unlock();}}catch(conststd::exceptione){std::cout手动加锁线程捕获异常e.what()\n;// 【修复提示】手动加锁必须在这里手动解锁否则死锁但新手极易遗漏// g_mutex.unlock(); // 若注释掉这行必然死锁解开则能解锁但代码易出错}}// lock_guard 加锁安全版本 voidincrementGuard(inttimes){try{for(inti0;itimes;i){// RAII 加锁构造时lock析构时自动unlock无论是否异常std::lock_guardstd::mutexlock(g_mutex);// 同样模拟临界区异常if(i
{std::coutlock_guard线程触发异常析构会自动解锁\n;throwstd::runtime_error(lock_guard临界区异常);}g_count;}}catch(conststd::exceptione){// 仅捕获异常无需手动解锁lock_guard已自动处理std::lock_guardstd::mutexlock(g_mutex);// 加锁保证输出不乱码std::coutlock_guard线程捕获异常e.what()\n;}}intmain(){constintthread_num5;constinttimes_per_thread1000;std::vectorstd::threadthreads;// 测试1手动加锁会死锁/计数异常 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;// 这行永远执行不到// 测试2lock_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的问题现象程序运行后会输出「手动加锁线程触发异常unlock() 将无法执行」然后卡死在t.join()处g_count的最终值永远打印不出来核心原因线程执行到i500时抛出异常跳过了后续的g_mutex.unlock()互斥锁被该线程永久持有其他线程调用g_mutex.lock()时会阻塞拿不到锁最终所有线程卡死g_count通常远低于 2500甚至只有几百即使在catch块中手动加g_mutex.unlock()解开代码中注释也能解锁但新手极易遗漏这行代码且代码逻辑会变得繁琐每个异常分支都要考虑解锁。
lock_guard 版本测试2的正常表现现象程序输出「lock_guard线程触发异常析构会自动解锁」无卡死最终g_count输出25005个线程×每个执行500次核心原因std::lock_guard是 RAII 封装构造时调用lock()无论正常执行还是异常退出作用域析构函数都会自动调用unlock()即使抛出异常锁也能被释放其他线程可正常竞争锁g_count能累加至预期值每个线程执行到500次抛异常总计 5×5002500。
总结手动加锁的核心缺陷异常会跳过unlock()语句导致锁永久占用死锁g_count累加异常即使手动在catch中解锁代码也易出错、可读性差lock_guard 的核心优势RAII 机制保证「加锁-解锁」成对出现无论临界区正常执行还是抛出异常都能自动释放锁避免死锁计数结果符合预期实战建议除非有特殊需求如手动控制解锁时机否则优先使用std::lock_guard或std::unique_lock避免手动调用lock()/unlock()。
如果想测试「手动加锁但在catch中解锁」的修复版本只需解开incrementManual中catch块里的g_mutex.unlock();注释此时程序不会死锁g_count也能输出 2500但代码复杂度远高于lock_guard版本。