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

从 0 开始讲透 C++ 并发(二):为什么需要 mutex?(数据竞争 + 解决方案)

承接上一篇:

C++并发编程第一篇:线程基础(对标Java,从0讲透)
👉 我们已经学会了:

✔ std::thread 创建线程
✔ lambda 在线程中的使用
✔ join / detach
✔ 生命周期问题

👉 但真正的并发问题,其实刚刚开始

一、问题:多线程同时操作变量会发生什么?

示例

#include <iostream> #include <thread> int main() { int a = 0; std::thread t1([&]{ for(int i = 0; i < 100000; i++) { a++; } }); std::thread t2([&]{ for(int i = 0; i < 100000; i++) { a++; } }); t1.join(); t2.join(); std::cout << a << std::endl; }

你以为: 200000 ❌

👉 实际:

随机值 ❗(比如 137842、189201…)

二、为什么会出错?(核心理解)


👉 关键点:

a++ 不是一个原子操作

👉 实际执行过程:
1️⃣ 读取 a
2️⃣ a + 1
3️⃣ 写回 a

👉 多线程同时执行:

线程1:读 a = 10
线程2:读 a = 10

线程1:写 11
线程2:写 11 ❌(覆盖)

👉 结果:数据丢失 → 最终结果错误

三、问题本质(必须理解🔥)

多个线程同时访问共享资源,并且至少有一个在写 → 就会出问题

👉 核心模型:

共享资源 + 并发写 = 数据竞争

对应第一篇:

线程已经能跑 ✔
但资源没有保护 ❌

四、解决方案:mutex(互斥锁🔥)

👉 思想非常简单:

同一时间,只允许一个线程访问资源

正确写法

#include <iostream> #include <thread> #include <mutex> int main() { int a = 0; std::mutex m; std::thread t1([&]{ for(int i = 0; i < 100000; i++) { std::lock_guard<std::mutex> lock(m); a++; } }); std::thread t2([&]{ for(int i = 0; i < 100000; i++) { std::lock_guard<std::mutex> lock(m); a++; } }); t1.join(); t2.join(); std::cout << a << std::endl; }

👉 输出:200000 ✔

五、lock_guard 是什么?(重点)

std::lock_guard<std::mutex> lock(m);

含义:

构造时加锁
作用域结束自动解锁

本质:

RAII(资源自动管理)

❌ 不推荐写法

m.lock();
a++;
m.unlock();

问题:中途异常 → unlock 不执行 → 死锁 ❗

所以必须用:

lock_guard(标准写法🔥)

六、Java synchronized 对比理解(帮助快速建立认知)

在理解mutex的时候,如果你有 Java 背景,可以这样对比:

Java 写法

synchronized (obj) { a++; }

含义:

进入代码块 → 自动加锁
退出代码块 → 自动释放锁

C++ 写法

std::mutex m; { std::lock_guard<std::mutex> lock(m); a++; }

含义:

lock_guard 构造 → m.lock()
lock_guard 析构 → m.unlock()

解释: std::lock_guard 创建锁,离开作用域,释放锁。

核心对比(必须理解🔥)

维度Java synchronizedC++ mutex
控制方式语言级(关键字)库级(类)
锁绑定代码块对象生命周期
自动释放✔(RAII)
使用方式简单更灵活

七、临界区(重要概念🔥)

👉 临界区:

需要被保护的代码区域

👉 示例:

{ std::lock_guard<std::mutex> lock(m); a++; // 临界区 }

原则: 临界区越小越好

八、一个常见错误(很多人会踩)

❌ 错误写法

std::lock_guard<std::mutex> lock(m); for(int i = 0; i < 100000; i++) { a++; }

👉 问题:

锁范围太大 → 性能极差

✅ 正确写法

for(...) { std::lock_guard<std::mutex> lock(m); a++; }

九、总结(笔记版)

数据竞争(Data Race): 原因: 多个线程同时访问共享变量(至少一个写) 问题: a++ 不是原子操作 → 数据错乱 解决: std::mutex + lock_guard 核心: 同一时间只允许一个线程访问 关键概念: 临界区 = 被保护的代码 原则: 锁范围越小越好 必须用 lock_guard(RAII)

十、一句话总结

mutex 的作用是保证共享资源在同一时间只能被一个线程访问,从而避免数据竞争问题

十一、你现在的进度(非常关键)

你已经完成:

✔ 第1篇:线程 + lambda
✔ 第2篇:数据竞争 + mutex

👉 你已经进入:

C++ 并发核心阶段🔥

下一篇(第3篇)

👉《C++ 并发(三):为什么需要 unique_lock?(比 lock_guard 更强大🔥)》

http://www.jsqmd.com/news/556433/

相关文章:

  • DDSP效果处理器详解:混响、FIR滤波与调制延迟的完整实现
  • Rolify 项目部署指南:从开发环境到生产环境的完整迁移流程
  • 阿里云盘生态观察:除了官方App,这些第三方资源搜索站是怎么火起来的?
  • 新手必看:用Python脚本自动计算磁盘容量和传输速率(附完整代码)
  • 如何用qmc-decoder解锁加密音乐:3步实现格式自由转换
  • Matlab科研绘图实战:饼图(Pie)的进阶美化与配色方案
  • 实时数据处理实战:使用 Apache Flink 消费 Kafka 数据并进行窗口聚合
  • 如何为Neutralinojs应用添加专业级窗口动画效果:终极实现指南
  • 智能体为什么这么火?
  • 影墨·今颜快速上手:英文Prompt写法+小红书审美风格控制技巧
  • 不止于‘看’:用Python玩转双光融合相机的数据采集与可视化分析
  • boxing裁剪功能深度优化:UCrop集成与自定义裁剪方案
  • 7天效率挑战:OpenClaw+Qwen3-32B镜像优化个人工作流
  • dry插件系统解析:如何扩展自定义Docker管理功能
  • 3个核心维度解析iOS数据取证:iLEAPP从入门到精通
  • 终极跨平台开发指南:ReScript Compiler在Windows/macOS/Linux的完整适配方案
  • 免费音频转换终极指南:用fre:ac轻松搞定音乐格式转换
  • STM32中断驱动下的EV1527无线解码实现与优化策略
  • PokemonRedExperiments强化学习训练中断恢复终极指南:checkpoint系统设计详解
  • Unblock-Youku测试与部署指南:从开发到上架Chrome商店
  • 【独家首发】Mojo 1.2 + Python 3.12混合编程标准架构图(工业级认证,仅限前500位开发者获取)
  • Netty从入门到精通:Java程序员必备!
  • Windows热键冲突终结者:Hotkey Detective技术解析与实战指南
  • 深入解析FOC控制中的Clark/Park变换及其Matplotlib动态仿真实现
  • 告别远程调试!手把手教你用DevEco Studio本地模拟器开发鸿蒙TV应用
  • 【图文教程】6大方法教你彻底禁止win11自动更新
  • ONNX-TensorRT 核心解析器深度解析:NvOnnxParser 架构与实现原理
  • 终极指南:如何用Chanlun-Pro实现智能缠论量化交易
  • NSwag安全访问控制配置指南:保护敏感API操作的终极方案
  • 摄影小白必看:你的手机拍照忽明忽暗?5分钟搞懂AE自动曝光与‘白加黑减’原理