0

0

c++中如何使用mutex互斥锁_c++线程安全与锁机制详解

尼克

尼克

发布时间:2026-01-06 10:19:16

|

513人浏览过

|

来源于php中文网

原创

必须用 RAII(如 std::lock_guard 或 std::unique_lock)管理 std::mutex,禁用手动 lock/unlock;多锁需用 std::lock 或 std::scoped_lock 避免死锁;mutex 不可复制/移动;锁粒度宜细,临界区忌 I/O 与耗时操作。

c++中如何使用mutex互斥锁_c++线程安全与锁机制详解

std::mutex 必须配合 std::lock_guard 或 std::unique_lock 使用

直接调用 mutex.lock()mutex.unlock() 极易出错:忘记 unlock、异常跳过 unlock、重复 unlock 都会导致未定义行为。C++ 标准库不鼓励手动管理锁生命周期。

正确做法是依赖 RAII —— 用 std::lock_guard作用域自动加锁/解锁)或 std::unique_lock(支持延迟锁定、转移所有权、条件变量配合)。

  • std::lock_guard 更轻量,构造即加锁,析构即解锁,不可复制,适合简单临界区
  • std::unique_lock 功能更全,但有轻微开销;若只用默认构造,它不持有锁,需显式调用 lock()
  • 绝不能把 std::lock_guard 声明在 if 分支或循环内却期望保护外层逻辑 —— 作用域决定生命周期

多个 mutex 同时加锁必须用 std::lock 避免死锁

当一段逻辑需要同时持有两个或以上 std::mutex 时,如果分别调用 m1.lock()m2.lock(),线程 A 先锁 m1 后等 m2,线程 B 先锁 m2 后等 m1,就会死锁。

标准解法是用 std::lock(m1, m2) —— 它使用“避免死锁算法”(如按地址排序尝试加锁),再配合 std::adopt_lock 构造 std::unique_lock

立即学习C++免费学习笔记(深入)”;

天谱乐
天谱乐

唱鸭旗下AI音乐创作平台,为您提供个性化音乐创作体验!

下载
std::mutex m1, m2;
std::lock(m1, m2); // 原子性获取两个锁
std::unique_lock guard1(m1, std::adopt_lock);
std::unique_lock guard2(m2, std::adopt_lock);
  • 不能对已 lock 的 mutex 再传给 std::lock,否则行为未定义
  • std::scoped_lock(C++17 起)是更简洁的替代:直接 std::scoped_lock<:mutex std::mutex> lock(m1, m2);,内部已调用 std::lock

std::mutex 不可复制、不可移动,成员变量声明要小心

std::mutex 是 move-only 类型(C++11 起禁用拷贝,也不提供移动构造/赋值),因此不能出现在需要拷贝的上下文中:

  • 类中声明 std::mutex mtx; 没问题,但该类自动删除拷贝构造函数和拷贝赋值运算符
  • 若类需可拷贝,不能把 std::mutex 作为直接成员;可改用 std::shared_ptr<:mutex>(注意共享本身不解决线程安全,只是绕过拷贝限制)
  • vector<:mutex> 编译失败;要用 std::vector<:unique_ptr>> 或预分配后 emplace_back
  • lambda 捕获 [mtx = std::move(mtx)] 无效 —— std::mutex 不可移动,捕获只能用引用或指针

性能陷阱:锁粒度太粗或临界区含阻塞操作

常见低效模式是把整个函数体包进一个 std::lock_guard,尤其当临界区内含 I/O、sleep、网络调用或复杂计算时,会严重拖慢并发吞吐。

应只保护真正共享数据读写的部分,其余可并发执行:

void process_data() {
    int local_val = expensive_computation(); // ✅ 可并发执行
    {
        std::lock_guard lock(mtx);
        shared_counter += local_val; // ✅ 仅此处需互斥
        update_log(shared_counter);  // ⚠️ 若 update_log 是 I/O,应移出临界区
    }
    log_to_file("processed"); // ✅ 移出后,多线程可并行写日志
}
  • 临界区内避免 std::coutprintf、文件写入、锁其他资源(如另一个 mutex)
  • 若必须在临界区做耗时操作,考虑用无锁结构(如 std::atomic)、读写锁(std::shared_mutex)或消息队列解耦
  • 调试时加锁日志(如 “enter critical section”)本身会放大竞争,上线前务必移除
锁本身不解决所有线程安全问题;它只是工具。真正容易被忽略的是:共享状态的设计是否必要、数据是否真的需要跨线程修改、能否用线程局部存储(thread_local)或消息传递替代共享。(mutex 只管“谁在用”,不管“用得对不对”)

相关专题

更多
java基础知识汇总
java基础知识汇总

java基础知识有Java的历史和特点、Java的开发环境、Java的基本数据类型、变量和常量、运算符和表达式、控制语句、数组和字符串等等知识点。想要知道更多关于java基础知识的朋友,请阅读本专题下面的的有关文章,欢迎大家来php中文网学习。

1458

2023.10.24

Go语言中的运算符有哪些
Go语言中的运算符有哪些

Go语言中的运算符有:1、加法运算符;2、减法运算符;3、乘法运算符;4、除法运算符;5、取余运算符;6、比较运算符;7、位运算符;8、按位与运算符;9、按位或运算符;10、按位异或运算符等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

227

2024.02.23

php三元运算符用法
php三元运算符用法

本专题整合了php三元运算符相关教程,阅读专题下面的文章了解更多详细内容。

85

2025.10.17

if什么意思
if什么意思

if的意思是“如果”的条件。它是一个用于引导条件语句的关键词,用于根据特定条件的真假情况来执行不同的代码块。本专题提供if什么意思的相关文章,供大家免费阅读。

719

2023.08.22

printf用法大全
printf用法大全

php中文网为大家提供printf用法大全,以及其他printf函数的相关文章、相关下载资源以及各种相关课程,供大家免费下载体验。

72

2023.06.20

fprintf和printf的区别
fprintf和printf的区别

fprintf和printf的区别在于输出的目标不同,printf输出到标准输出流,而fprintf输出到指定的文件流。根据需要选择合适的函数来进行输出操作。更多关于fprintf和printf的相关文章详情请看本专题下面的文章。php中文网欢迎大家前来学习。

279

2023.11.28

printf用法大全
printf用法大全

php中文网为大家提供printf用法大全,以及其他printf函数的相关文章、相关下载资源以及各种相关课程,供大家免费下载体验。

72

2023.06.20

fprintf和printf的区别
fprintf和printf的区别

fprintf和printf的区别在于输出的目标不同,printf输出到标准输出流,而fprintf输出到指定的文件流。根据需要选择合适的函数来进行输出操作。更多关于fprintf和printf的相关文章详情请看本专题下面的文章。php中文网欢迎大家前来学习。

279

2023.11.28

PPT动态图表制作教程大全
PPT动态图表制作教程大全

本专题整合了PPT动态图表制作相关教程,阅读专题下面的文章了解更多详细内容。

3

2026.01.07

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
React 教程
React 教程

共58课时 | 3.4万人学习

Pandas 教程
Pandas 教程

共15课时 | 0.9万人学习

ASP 教程
ASP 教程

共34课时 | 3.3万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号