当前位置: 首页 > news >正文

std::mutex与std::lock

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

文章目录

  • `std::mutex` 与 `std::lock` 详解及使用指南
    • 一、`std::mutex` 详解
      • 1. 什么是 `std::mutex`
      • 2. `std::mutex` 的核心成员函数
      • 3. `std::mutex` 的基本使用(不推荐直接用,仅作示例)
        • 示例1:基础使用(存在风险)
        • 示例2:直接使用的缺陷(异常导致死锁)
      • 4. 改进:RAII 风格的锁管理(推荐使用)
        • (1)`std::lock_guard`:轻量级RAII锁(推荐用于简单场景)
        • (2)`std::unique_lock`:灵活的RAII锁(推荐用于复杂场景)
    • 二、`std::lock` 详解
      • 1. 什么是 `std::lock`
      • 2. `std::lock` 的核心特点
      • 3. `std::lock` 的使用场景
        • 死锁示例(依次加锁导致)
      • 4. `std::lock` 解决死锁(推荐用法)
        • 说明:
    • 三、总结与注意事项
      • 1. 核心总结
      • 2. 关键注意事项
      • 3. 编译运行命令(GCC)

std::mutexstd::lock详解及使用指南

std::mutexstd::lock是 C++11 标准库中用于多线程同步的核心组件,主要解决数据竞争死锁问题。其中,std::mutex是基础的互斥锁类型,而std::lock是用于安全锁定多个互斥锁的函数模板,二者常配合使用。

一、std::mutex详解

1. 什么是std::mutex

std::mutex(互斥量,Mutual Exclusion)是 C++ 标准库提供的基础互斥锁,属于<mutex>头文件。它的核心作用是保护临界区(多个线程同时访问的共享资源代码段),确保同一时间只有一个线程能进入临界区,从而避免数据竞争(未定义行为)。

std::mutex非递归锁(也叫非重入锁),即同一个线程不能对同一个std::mutex多次调用lock()(否则会导致死锁)。

2.std::mutex的核心成员函数

成员函数功能说明
lock()加锁:如果锁已被其他线程占用,当前线程会阻塞直到获得锁。
try_lock()尝试加锁:成功返回true;失败(锁被占用)返回false不会阻塞
unlock()解锁:释放锁,必须由持有锁的线程调用(否则行为未定义)。
native_handle()获取底层操作系统的锁句柄(用于高级操作,跨平台兼容性差)。

3.std::mutex的基本使用(不推荐直接用,仅作示例)

直接调用lock()unlock()是最基础的用法,但存在严重缺陷(如忘记解锁、异常导致解锁失败),仅用于理解原理。

示例1:基础使用(存在风险)
#include<iostream>#include<thread>#include<mutex>#include<vector>// 共享资源intg_count=0;// 互斥锁:保护g_countstd::mutex g_mutex;// 累加函数voidincrement(inttimes){for(inti=0;i<times;++i){// 加锁:进入临界区g_mutex.lock();// 临界区:操作共享资源g_count++;// 解锁:退出临界区g_mutex.unlock();// 模拟耗时操作(锁外执行,减少阻塞)std::this_thread::sleep_for(std::chrono::nanoseconds(1));}}intmain(){constintthread_num=5;constinttimes_per_thread=1000;std::vector<std::thread>threads;// 创建线程for(inti=0;i<thread_num;++i){threads.emplace_back(increment,times_per_thread);}// 等待线程结束for(auto&t:threads){t.join();}std::cout<<"最终count值:"<<g_count<<std::endl;// 正确输出5000return0;}
示例2:直接使用的缺陷(异常导致死锁)

如果临界区中抛出异常,unlock()不会被执行,锁会被永久占用,导致其他线程死锁:

voidincrementWithException(inttimes){for(inti=0;i<times;++i){g_mutex.lock();// 模拟异常:比如除以0if(i==500){throwstd::runtime_error("意外异常");// 异常抛出,unlock()不会执行}g_count++;g_mutex.unlock();// 永远执行不到}}

4. 改进:RAII 风格的锁管理(推荐使用)

为了解决手动加锁/解锁的缺陷,C++ 提供了RAII(资源获取即初始化)风格的锁管理类,它们会在构造时自动加锁析构时自动解锁(即使发生异常),从根本上避免死锁。

(1)std::lock_guard:轻量级RAII锁(推荐用于简单场景)

std::lock_guard是最简单的 RAII 锁,功能单一:构造加锁,析构解锁,不支持手动控制锁的生命周期。

示例:用std::lock_guard修复异常问题

#include<iostream>#include<thread>#include<mutex>#include<vector>#include<stdexcept>intg_count=0;std::mutex g_mutex;voidincrementSafe(inttimes){for(inti=0;i<times;++i){// RAII:构造时加锁,析构时解锁(即使异常也会解锁)std::lock_guard<std::mutex>lock(g_mutex);// 模拟异常if(i==500){// 异常抛出时,lock的析构函数会被调用,自动解锁throwstd::runtime_error("意外异常");}g_count++;}}intmain(){constintthread_num=5;constinttimes_per_thread=1000;std::vector<std::thread>threads;try{for(inti=0;i<thread_num;++i){threads.emplace_back(incrementSafe,times_per_thread);}for(auto&t:threads){t.join();}}catch(conststd::exception&e){std::cout<<"捕获异常:"<<e.what()<<std::endl;}std::cout<<"最终count值:"<<g_count<<std::endl;return0;}
(2)std::unique_lock:灵活的RAII锁(推荐用于复杂场景)

std::unique_lockstd::lock_guard更灵活,支持:

  • 手动加锁/解锁(lock()/unlock());
  • 延迟加锁(构造时不加锁,后续手动调用lock());
  • 尝试加锁(try_lock());
  • 超时加锁(try_lock_for()/try_lock_until());
  • 配合条件变量(std::condition_variable)使用;
  • 转移锁的所有权(支持移动语义,不可复制)。

示例:std::unique_lock的灵活使用

#include<iostream>#include<thread>#include<mutex>#include<vector>intg_count=0;std::mutex g_mutex;voidincrementFlexible(inttimes){for(inti=0;i<times;++i){// 延迟加锁:构造时不加锁,后续手动加锁std::unique_lock<std::mutex>lock(g_mutex,std::defer_lock);// 手动加锁(也可以用try_lock()尝试加锁)lock.lock();g_count++;// 手动解锁(可选:析构时会自动解锁,这里提前解锁以减少阻塞)lock.unlock();// 锁已释放,其他线程可进入临界区std::this_thread::sleep_for(std::chrono::nanoseconds(1));}}intmain(){constintthread_num=5;constinttimes_per_thread=1000;std::vector<std::thread>threads;for(inti=0;i<thread_num;++i){threads.emplace_back(incrementFlexible,times_per_thread);}for(auto&t:threads){t.join();}std::cout<<"最终count值:"<<g_count<<std::endl;return0;}

二、std::lock详解

1. 什么是std::lock

std::lock是 C++11 提供的函数模板(位于<mutex>头文件),不是锁类型。它的核心作用是同时锁定多个互斥锁,并采用避免死锁的算法(如按互斥锁的内存地址顺序加锁),确保要么所有锁都被成功锁定,要么一个都不锁定(如果其中一个锁被占用,会释放已锁定的锁,等待后重试)。

2.std::lock的核心特点

  • 接收多个互斥锁的引用作为参数(至少两个);
  • 原子操作:要么全部锁定,要么全部不锁定;
  • 避免死锁:内部实现了死锁避免逻辑(如按地址排序加锁);
  • 不管理锁的生命周期:锁定后需要手动解锁(或配合 RAII 类的std::adopt_lock参数)。

3.std::lock的使用场景

当一个线程需要同时访问多个共享资源(对应多个互斥锁)时,直接依次加锁可能导致死锁,此时用std::lock可以安全锁定多个锁。

死锁示例(依次加锁导致)

两个线程同时尝试锁定两个互斥锁,但顺序相反,导致互相等待:

#include<iostream>#include<thread>#include<mutex>std::mutex m1,m2;// 线程1:先锁m1,再锁m2voidthread1Func(){for(inti=0;i<1000;++i){m1.lock();m2.lock();// 可能被线程2的m2.lock()阻塞,此时线程1持有m1,线程2持有m2,死锁// 临界区:操作共享资源std::cout<<"线程1执行临界区"<<std::endl;m2.unlock();m1.unlock();}}// 线程2:先锁m2,再锁m1voidthread2Func(){for(inti=0;i<1000;++i){m2.lock();m1.lock();// 可能被线程1的m1.lock()阻塞,导致死锁// 临界区:操作共享资源std::cout<<"线程2执行临界区"<<std::endl;m1.unlock();m2.unlock();}}intmain(){std::threadt1(thread1Func);std::threadt2(thread2Func);t1.join();t2.join();return0;}

上述代码运行后,大概率会出现死锁,程序卡死。

4.std::lock解决死锁(推荐用法)

使用std::lock同时锁定多个互斥锁,配合std::unique_lockstd::adopt_lock参数(表示锁已被手动锁定,由unique_lock接管生命周期):

#include<iostream>#include<thread>#include<mutex>std::mutex m1,m2;// 线程1:用std::lock同时锁m1和m2voidthread1Func(){for(inti=0;i<1000;++i){// 步骤1:创建unique_lock,延迟加锁std::unique_lock<std::mutex>lock1(m1,std::defer_lock);std::unique_lock<std::mutex>lock2(m2,std::defer_lock);// 步骤2:同时锁定m1和m2,避免死锁std::lock(lock1,lock2);// 也可以写std::lock(m1, m2)// 临界区:操作共享资源std::cout<<"线程1执行临界区"<<std::endl;// 步骤3:析构时自动解锁(无需手动unlock)}}// 线程2:同样用std::lock同时锁m1和m2(顺序无关)voidthread2Func(){for(inti=0;i<1000;++i){std::unique_lock<std::mutex>lock1(m1,std::defer_lock);std::unique_lock<std::mutex>lock2(m2,std::defer_lock);std::lock(lock2,lock1);// 顺序与线程1相反,仍不会死锁// 临界区:操作共享资源std::cout<<"线程2执行临界区"<<std::endl;}}intmain(){std::threadt1(thread1Func);std::threadt2(thread2Func);t1.join();t2.join();return0;}
说明:
  • std::defer_lock:表示unique_lock构造时不自动加锁;
  • std::lock(lock1, lock2):同时锁定两个unique_lock关联的互斥锁(也可以直接传m1, m2);
  • std::adopt_lock(可选):如果直接调用std::lock(m1, m2)(手动锁定),则需要用std::unique_lock<std::mutex> lock1(m1, std::adopt_lock)表示接管已锁定的锁。

三、总结与注意事项

1. 核心总结

组件定位核心用途推荐场景
std::mutex基础互斥锁类型保护单个临界区,实现基本同步作为底层锁,配合RAII类使用
std::lock_guard轻量级RAII锁简单临界区的自动加锁/解锁无复杂逻辑的临界区(优先使用)
std::unique_lock灵活的RAII锁复杂场景(延迟加锁、超时、条件变量)需手动控制锁生命周期的场景
std::lock多锁同步函数模板同时锁定多个互斥锁,避免死锁需访问多个共享资源(多个mutex)的场景

2. 关键注意事项

  1. 避免直接使用std::mutexlock()/unlock():优先使用std::lock_guardstd::unique_lock,利用RAII避免死锁。
  2. std::mutex是非递归锁:同一线程不能多次锁定同一个std::mutex(会死锁),如果需要递归锁,使用std::recursive_mutex(但递归锁易导致逻辑混乱,尽量避免使用)。
  3. 临界区尽可能小:只在必要的代码段加锁,减少线程阻塞时间,提升并发性能。
  4. 多锁场景必须用std::lock:不要手动依次加锁,否则极易导致死锁。
  5. 编译时链接线程库:使用C++多线程时,GCC/Clang需加-pthread参数,MSVC需启用线程支持。

3. 编译运行命令(GCC)

# 编译示例代码g++ -std=c++11 mutex_lock_demo.cpp -o mutex_lock_demo -pthread# 运行./mutex_lock_demo
http://www.cnnetsun.cn/news/851014.html

相关文章:

  • 小程序毕设选题推荐:基于微信小程序的驾校预约系统的设计与实现基于SpringBoot与微信小程序的驾校预约管理系统设计与实现【附源码、mysql、文档、调试+代码讲解+全bao等】
  • 基于k-means聚类的图像区域分割[有报告]图像处理聚类区域分割(设计源文件+万字报告+讲解)(支持资料、图片参考_相关定制)_文章底部可以扫码
  • 2026年都有哪些值得推荐的低代码?2026年主流低代码平台全景洞察与预测盘点
  • 基于SpringBoot + Vue的麻将机售卖平台
  • 企业智能仓储网络系统的设计与实现
  • WEB 作业 即时内容发布前端交互案例
  • django-flask基于Python可视化的学习做题答题统计系统的设计与实现vue
  • django-flask基于Python的高校学生成绩分析vue 论文
  • 不想当背锅侠,这6款监控工具一定要会!(Zabbix、Prometheus等常见监控教程)
  • 【OpenCV】Python图像处理矩特征之矩的计算/计算轮廓的面积
  • 多模态大模型中Attention机制暗藏「骗局」,需用一个公式修正丨上大×南开
  • 2026必备!9个AI论文写作软件,MBA论文写作神器推荐!
  • 基于SpringBoot的私房菜定制上门服务系统毕设源码
  • AI应用架构师指南:构建AI驱动数学研究的方法论体系
  • Node.js 全局对象
  • Android系列之 屏幕触控机制(一)
  • Thinkphp和Laravel基于hadoop大数据的心脏病患者健康数据分析系统_
  • 408真题解析-2010-17-计组-TLB\Cache\Page关系
  • 免费DirectX修复工具——2026最新5种方法快速修复DirectX报错:AI一键解决最省心!
  • [特殊字符] Go语言从入门到实践(一):为什么Go能让程序员“少加班“?
  • 数据跨境、隐私泄露、审计溯源——出海企业三大安全必答题
  • 基于Spring Boot开发的大学生校内自习室座位预约系统源码+文档
  • Node.js 创建第一个应用
  • 手动处理CSV转Excel?Python批量转格式,不用逐个开文件
  • 基于深度学习YOLOv10的船舶分类识别检测系统(YOLOv10+YOLO数据集+UI界面+Python项目源码+模型)
  • 健康管理实训室厂家,一站式解决方案供应
  • Python 列表(List)
  • Spring Boot 异步编程:@Async 与线程池配置的最佳实践终极指南
  • Excel ADDRESS函数深度解析:动态构建单元格地址的艺术
  • 微信表情GIF传不上?GIF压缩到微信表情不模糊方法