c++怎么处理多线程死锁_c++ lock_guard与unique_lock锁管理【技巧】
技术百科
冰火之心
发布时间:2026-01-01
浏览: 次 死锁本质是资源获取顺序不一致,解决关键是统一加锁顺序;优先用std::scoped_lock处理多锁,单锁用std::lock_guard,需灵活控制时用std::unique_lock配合条件变量。
死锁根本不是锁的问题,而是资源获取顺序不一致
死锁在 C++ 多线程中几乎总是因为多个线程以不同顺序请求同一组互斥量(std::mutex)导致。比如线程 A 先锁 mtx_a 再锁 mtx_b,而线程 B 反过来先锁 mtx_b 再锁 mtx_a——只要两个线程执行节奏稍有交错,就卡死。
解决思路不是换锁类型,而是统一加锁顺序。常见做法包括:
- 给所有互斥量定义全局唯一序号,按序号从小到大加锁(用
std::scoped_lock可自动完成) - 避免在持有锁期间调用可能获取其他锁的函数(尤其是第三方或虚函数)
- 绝不手动调用
lock()/unlock();裸调用是死锁温床
std::lock_guard 适合“进作用域即锁,出作用域即放”的简单场景
std::lock_guard 是最轻量、最安全的 RAII 锁包装器,构造时立即加锁,析构时必然释放,不可转移、不可复制、不可延迟加锁。
它适用于:单个 mutex 的短临界区、不需要条件等待、不涉及多个锁的同步。
std::mutex mtx;
void safe_update() {
std::lock_guard guard(mtx); // 构造即 lock()
// ... 临界区操作
} // 出作用域,guard 析构,自动 unlock()
注意:lock_guard 不支持 try_lock()、不支持 unlock() 提前释放、不能用于 std::condition_variable::wait() ——这些都得换 std::unique_lock。
std::unique_lock 是灵活但需更谨慎的锁管理器
std::unique_lock 支持延迟加锁、手动解锁、条件变量配合、可移动(用于返回锁、传入函数),但灵活性带来责任:忘记 lock() 或重复 unlock() 会引发未定义行为。
典型误用:
- 声明
后没调 lock()就进临界区 → 数据竞争 - 调了
unlock()后又让其析构 → 二次 unlock → UB - 传给
wait()后没检查条件就继续用被临时释放的锁 → 逻辑错乱
正确用法示例(配合条件变量):
std::mutex mtx;
std::condition_variable cv;
bool ready = false;
// 等待方
void wait_for_ready() {
std::unique_lock ulock(mtx);
cv.wait(ulock, []{ return ready; }); // wait 内部会 unlock + 唤醒后重新 lock
// 此处 ulock 已重新持有 mtx,可安全访问 shared data
}
// 通知方
void set_ready() {
std::lock_guard guard(mtx);
ready = true;
cv.notify_one();
}
优先用 std::scoped_lock 解决多锁死锁
C++17 引入的 std::scoped_lock 是处理多个互斥量的首选:它原子性地获取所有锁,内部自动按地址排序(或使用 ADL std::lock 协议),彻底规避因加锁顺序不一致导致的死锁。
对比 lock_guard(只支持一个锁)和手写多 lock()(易出错),scoped_lock 更简洁可靠:
std::mutex mtx_a, mtx_b;
void transfer(int amount) {
// 安全:自动避免死锁
std::scoped_lock lock(mtx_a, mtx_b);
// ... 同时操作两个资源
}
注意:scoped_lock 构造失败(如某 mutex 不可 lock)会抛 std::system_error,且不提供 try_lock 变体——如需非阻塞,应改用 std::try_to_lock_t + unique_lock 组合。
真正容易被忽略的是:即使用了 scoped_lock,如果临界区内又间接触发了其他锁(比如调用了一个你没看源码的库函数),死锁依然可能发生。锁管理只是工具,资源访问契约才是关键。
# ai
# 的是
# 才是
# 尤其是
# 多个
# 适用于
# 不需要
# 不支持
# 工具
# c++
# 线程
# 死锁
# red
# 多线程
# 作用域
# 虚函数
# 加锁
# 有锁
# 一加
# 互斥
相关栏目:
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
AI推广<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
SEO优化<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
技术百科<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
谷歌推广<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
百度推广<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
网络营销<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
案例网站<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
精选文章<?muma echo $count; ?>
】
相关推荐
- Win11笔记本怎么看电池健康度_Win11电池报
- c++中如何对数组进行排序_c++数组排序算法汇总
- Win11怎么关闭右下角弹窗_Win11拦截系统通
- 如何使用Golang理解结构体指针方法接收者_Go
- LINUX下如何配置VLAN虚拟局域网_在LINU
- Win11截图快捷键是什么_Win11自带截图工具
- 如何使用Golang优化模块引入路径_Golang
- c++ atoi和atof函数用法_c++字符数组
- Win10怎么卸载爱奇艺_Win10彻底卸载爱奇艺
- 手机php怎么转mp4_手机端php文件转mp4a
- Win11关机快捷键是什么_Win11快速关机方法
- php8.4如何调用com组件_php8.4win
- c++中如何进行二进制文件读写_c++ read与
- 如何在Golang中定义接口_抽象方法和多态实现
- Windows怎样关闭桌面弹窗广告_Windows
- Win11怎么关闭定位服务 Win11禁止应用获取
- Win10怎么查看内存时序参数_Win10CPU-
- Win10怎么更改用户名 Win10修改账户名称操
- Python对象比较与排序_集合使用说明【指导】
- c# 在高并发场景下,委托和接口调用的性能对比
- How to Properly Use NumPy
- Win11无法安装软件怎么办_Win11解除应用安
- Windows如何设置登录时的欢迎屏幕背景?(锁屏
- 一文教你快速开通网站LOGO图
- Python字符串操作教程_切片拼接与格式化详解
- Mac的“调度中心”与“空间”怎么用_Mac多桌面
- Windows10怎样设置家长控制_Windows
- 如何在 Django 中修改用户密码后保持会话不丢
- PHP主流架构怎么部署到Docker_容器化流程【
- Python变量绑定机制_引用模型解析【教程】
- Win11怎么清理C盘下载文件夹_Win11清理下
- C++如何使用std::optional?(处理可
- Win10怎样安装PPT模板_Win10安装PPT
- Win11搜索栏无法输入_解决Win11开始菜单搜
- MAC怎么一键隐藏桌面所有图标_MAC极简模式切换
- MAC如何隐藏文件夹及文件_MAC终端命令隐藏与第
- 如何自定义Windows终端的默认配置文件?(Po
- Linux如何挂载新硬盘_Linux磁盘分区格式化
- 电脑的“网络和共享中心”去哪了_Windows 1
- php订单日志怎么记录物流_php记录订单物流变更
- Win11怎么格式化U盘_Win11系统U盘格式化
- 如何理解Go指针和内存分配关系_Go Pointe
- 如何在Golang中修改数组元素_通过指针实现原地
- PHP主流架构如何处理会话管理_Session与C
- 如何使用Golang构建简易投票统计功能_Gola
- 如何使用Golang操作指针变量_Golang解引
- php在Linux怎么部署_LNMP环境搭建PHP
- 如何使用Golang实现基本类型比较_Golang
- Win10系统怎么查看显卡温度_Win10任务管理
- Win11怎么打开注册表_Windows 11注册

后没调
QQ客服