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

C++ 多线程与并发系统取向(二)—— 资源保护:std::mutex 与 RAII(类比 Java synchronized)

一、先问一个工程问题

你有一个共享容器:

std::vector<int> data;

两个线程同时往里面push_back()

会发生什么?

你可能会说:

vector 不是自动扩容的吗?

是的,但:

vector 不是线程安全的。

内部会:

  • 修改 size
  • 可能 realloc
  • 移动内存

如果两个线程同时写:

未定义行为。

二、资源保护的本质

并发系统里最重要一句话:

不是“给代码加锁”,而是“给资源加保护策略”。

资源 = 被多个线程共享的数据。

三、什么是临界区(Critical Section)

临界区是:

访问共享资源的代码区域。

例如:

data.push_back(1);

这一行,就是临界区。

四、最原始写法(危险)

std::mutex mtx; void task() { mtx.lock(); data.push_back(1); mtx.unlock(); }

看起来没问题。

但如果中间抛异常?

mtx.lock(); throw std::runtime_error("error"); mtx.unlock(); // 永远执行不到

锁永远不释放。

其他线程:

永久阻塞(死锁)。

五、C++ 的核心思想:RAII

RAII = Resource Acquisition Is Initialization

意思:

资源的获取与对象生命周期绑定。

锁对象在构造时加锁,在析构时自动解锁。

这就是:

std::lock_guard

六、正确写法:std::lock_guard

std::mutex mtx; void task() { std::lock_guard<std::mutex> lock(mtx); data.push_back(1); }

作用:

  • 构造 → 自动 lock
  • 离开作用域 → 自动 unlock

无论:

  • return
  • 异常
  • break

都会释放锁。

七、Java 对比:synchronized

Java 写法:

synchronized (lock) { data.add(1); }

本质类似:

  • 进入代码块 → 加锁
  • 离开代码块 → 自动释放

区别:

C++Java
lock_guard 是对象synchronized 是语言关键字
手动控制 mutexJVM 管理 monitor

八、完整并发示例(安全版)

#include <iostream> #include <thread> #include <vector> #include <mutex> std::vector<int> data; std::mutex mtx; void task() { for (int i = 0; i < 10000; ++i) { std::lock_guard<std::mutex> lock(mtx); data.push_back(i); } } int main() { std::thread t1(task); std::thread t2(task); t1.join(); t2.join(); std::cout << data.size() << std::endl; }

输出:

20000

稳定。

九、锁的粒度问题(工程重点)

你这样写:

void task() { std::lock_guard<std::mutex> lock(mtx); for (int i = 0; i < 10000; ++i) { data.push_back(i); } }

锁范围更大。

优点:

  • 锁竞争少

缺点:

  • 阻塞时间更长

工程原则:

锁只保护“共享资源操作”,
不要把耗时操作放在锁里。

十、错误示例:锁太小

if (!data.empty()) { std::lock_guard<std::mutex> lock(mtx); data.pop_back(); }

问题:

data.empty()没加锁。

可能:

线程 A 判断非空
线程 B pop 完
线程 A 再 pop → 崩溃

正确写法:

std::lock_guard<std::mutex> lock(mtx); if (!data.empty()) { data.pop_back(); }

十一、资源保护系统思维

写并发代码前问:

1️⃣ 这个数据是否共享?
2️⃣ 是否有写操作?
3️⃣ 锁由谁持有?
4️⃣ 锁范围是否最小化?

十二、死锁风险

死锁发生在:

  • 两个线程
  • 持有不同锁
  • 相互等待

例如:

线程 A:锁 m1 → 锁 m2
线程 B:锁 m2 → 锁 m1

工程规避方式:

统一加锁顺序。

十三、工程 Checklist

✅ 共享数据必须有唯一 mutex
✅ 永远使用 lock_guard(不要手动 lock/unlock)
✅ 判断 + 操作必须在同一锁内
✅ 锁只保护数据,不保护耗时操作


十四、Java 再类比总结

概念JavaC++
自动释放锁synchronizedlock_guard
手动锁ReentrantLockstd::mutex
异常安全JVM 保证RAII 保证

十五、本篇总结一句话

线程私有栈,共享堆;
共享必保护;
RAII 是 C++ 锁的灵魂。

下一篇预告

第三篇我们讲:

std::unique_lock —— 为什么它比 lock_guard 更“重”?

  • 为什么条件变量必须用 unique_lock?
  • 如何避免死锁?
  • defer_lock / try_lock 是干什么的?
  • 锁释放与再获取的工程场景
http://www.jsqmd.com/news/403282/

相关文章:

  • 深入理解 Java join:它到底做了什么?和协程挂起有什么区别?
  • Java 版 Claude Code CLI 来了!(国产开源)Solon Code CLI 发布
  • [AI提效-15]-豆包多对话功能详解:打破传统AI工具“单对话单一主题、新对话从零起步”的局限,高效衔接创作,告别反复沟通成本。
  • SpringBoot整合ES8向量检索:构建高精度智能客服系统的工程实践
  • 幽灵客户端
  • 电商智能客服Agent工作流架构优化实战:从高延迟到毫秒级响应
  • 视频孪生平台之上:镜像视界三维实时解算体系在港口与机场复杂场景协同治理中的全球领先性研究
  • 视频孪生平台之上:镜像视界三维实时解算体系在公安空间治安操作系统中的全球领先性研究
  • 大规模语言模型的反事实推理在战略规划中的多维度应用
  • 解读AI原生应用领域差分隐私的重要性
  • C++罗马曲面3D旋转程序代码解说_C++精灵库应用案例
  • 【开题答辩全过程】以 高校实验室管理系统为例,包含答辩的问题和答案
  • AI应用架构师困惑:AI驱动芯片设计,数据不够怎么办?
  • [use agent-browser not playwright directly]
  • AI-RAN Sionna开发者套件全解析:从入门到集群,开启无线通信与AI融合开发新纪元
  • AI原生应用领域自主代理的故障诊断与修复
  • Vue实现智能客服对话框与推荐问题展示:从架构设计到AI集成实战
  • 如何在WSL中设置AMD AI MAX 395的Rocm微调环境
  • Flink实时计算心智模型——流、窗口、水位线、状态与Checkpoint的协作
  • 百度AI智能客服Prompt设置实战:从零搭建高效对话系统的避坑指南
  • 2024提示工程安全趋势:加密传输机制的3个创新方向
  • 【GitHub项目推荐--Heretic:全自动语言模型去审查工具】⭐⭐⭐
  • 【GitHub项目推荐--Flet:Python全栈开发者的跨平台应用框架】⭐
  • 智能客服转人工:从架构设计到实战避坑指南
  • Node.js运维部署实战:从0到1开始搭建Node.js运行环境
  • 修复网页失效的css
  • 倒立摆系统MPC控制MATLAB代码功能说明
  • 近况报告(II)
  • 北京大兴区附近回收黄金店实测,在跑了三家之后,我更看重这三点
  • 基于MCP的智能客服系统搭建:从架构设计到性能优化实战