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

C++ 多线程同步机制详解:互斥锁、条件变量与原子操作

互斥锁 (Mutex)

核心概念

互斥锁是最基本的同步机制,用于保护共享资源,防止多个线程同时访问同一资源导致的数据竞争问题。

工作原理

  1. 线程访问共享资源前尝试获取锁
  2. 如果锁可用,线程获得锁并访问资源
  3. 如果锁不可用,线程阻塞等待
  4. 完成访问后释放锁,其他线程可以获取

C++ 实现

#include <mutex>std::mutex mtx; // 声明互斥锁void critical_section() {mtx.lock();   // 获取锁// 访问共享资源mtx.unlock(); // 释放锁
}

RAII 包装器(推荐)

void safe_critical_section() {std::lock_guard<std::mutex> lock(mtx); // 构造时自动加锁// 访问共享资源// 析构时自动解锁
}

高级用法:std::unique_lock

void flexible_critical_section() {std::unique_lock<std::mutex> lock(mtx, std::defer_lock); // 延迟加锁// ... 非临界区操作 ...lock.lock(); // 手动加锁// 访问共享资源lock.unlock(); // 可提前解锁// ... 其他非临界区操作 ...// 离开作用域时,如果仍持有锁,自动解锁
}

特点

  • 阻塞式同步:获取不到锁时线程会阻塞
  • 非递归:同一线程重复加锁会导致死锁
  • 不可拷贝/移动:保证锁的所有权明确

条件变量 (Condition Variable)

核心概念

条件变量用于线程间的通信,允许线程在特定条件成立前阻塞等待,条件成立后被唤醒。

工作原理

  1. 线程获取互斥锁
  2. 检查条件是否满足
  3. 如果不满足,调用wait()释放锁并阻塞
  4. 其他线程修改条件后通知等待线程
  5. 被通知线程重新获取锁并检查条件

C++ 实现

#include <mutex>
#include <condition_variable>std::mutex mtx;
std::condition_variable cv;
bool ready = false;void waiting_thread() {std::unique_lock<std::mutex> lock(mtx);cv.wait(lock, []{ return ready; }); // 等待条件成立// 条件满足后执行操作
}void notifying_thread() {{std::lock_guard<std::mutex> lock(mtx);ready = true;}cv.notify_one(); // 通知一个等待线程// cv.notify_all(); // 通知所有等待线程
}

关键点

  1. 总是与互斥锁配合使用
  2. 使用谓词避免虚假唤醒
// 正确用法:使用谓词检查条件cv.wait(lock, []{ return condition; });// 错误用法:可能因虚假唤醒导致问题while (!condition) {cv.wait(lock);}
  1. 通知时机
    • notify_one():唤醒一个等待线程
    • notify_all():唤醒所有等待线程

适用场景

  • 生产者-消费者问题
  • 线程池任务调度
  • 事件驱动型架构

原子操作 (Atomic Operations)

核心概念

原子操作提供无锁同步机制,保证对基本数据类型的操作不可分割,无需显式加锁。

工作原理

  • 利用CPU的原子指令实现
  • 保证操作的原子性(不可中断)
  • 提供内存顺序控制

C++ 实现

#include <atomic>std::atomic<int> counter(0); // 原子整型void increment() {counter.fetch_add(1, std::memory_order_relaxed); // 原子自增
}void decrement() {counter.fetch_sub(1, std::memory_order_relaxed); // 原子自减
}

内存顺序

C++ 提供6种内存顺序,控制操作间的可见性和顺序性:

内存顺序特点性能
memory_order_seq_cst顺序一致性(默认)最慢
memory_order_acquire获取操作,阻止之后的读/写重排中等
memory_order_release释放操作,阻止之前的读/写重排中等
memory_order_acq_rel获取-释放组合中等
memory_order_consume消费操作(已弃用)-
memory_order_relaxed无顺序约束最快

常用操作

std::atomic<int> value;// 存储值
value.store(42, std::memory_order_release);// 加载值
int x = value.load(std::memory_order_acquire);// 交换值
int old = value.exchange(100);// 比较交换(CAS)
int expected = 100;
bool success = value.compare_exchange_strong(expected, 200);

适用场景

  • 计数器、标志位等简单共享变量
  • 无锁数据结构实现
  • 性能敏感的同步场景

三种机制对比

特性互斥锁条件变量原子操作
同步方式阻塞式阻塞式+通知非阻塞式
性能开销高(上下文切换)
适用场景复杂临界区条件等待简单变量操作
实现复杂度中等
内存开销较大(锁对象)较大(锁+条件变量)
死锁风险
可扩展性差(锁竞争)中等

综合示例:线程安全队列

#include <queue>
#include <mutex>
#include <condition_variable>
#include <atomic>template <typename T>
class ThreadSafeQueue {
public:void push(T value) {{std::lock_guard<std::mutex> lock(mtx);queue.push(std::move(value));}cv.notify_one();}bool try_pop(T& value) {std::lock_guard<std::mutex> lock(mtx);if (queue.empty()) return false;value = std::move(queue.front());queue.pop();return true;}bool wait_pop(T& value) {std::unique_lock<std::mutex> lock(mtx);cv.wait(lock, [this]{ return !queue.empty() || stopped; });if (stopped) return false;value = std::move(queue.front());queue.pop();return true;}void stop() {{std::lock_guard<std::mutex> lock(mtx);stopped = true;}cv.notify_all();}size_t size() const {std::lock_guard<std::mutex> lock(mtx);return queue.size();}private:mutable std::mutex mtx;std::condition_variable cv;std::queue<T> queue;std::atomic<bool> stopped{false};
};

使用建议

  1. 优先考虑原子操作:对于简单计数器、标志位等场景
  2. 复杂同步使用互斥锁:当需要保护复杂数据结构时
  3. 条件变量用于等待通知:当线程需要等待特定条件时
  4. 遵循RAII原则:使用lock_guardunique_lock管理锁
  5. 避免嵌套锁:容易导致死锁
  6. 注意虚假唤醒:条件变量等待总是使用谓词
  7. 谨慎选择内存顺序:原子操作默认使用memory_order_seq_cst,高性能场景可考虑更宽松的顺序
http://www.xdnf.cn/news/1186255.html

相关文章:

  • 电子电气架构 --- 车载软件与样件产品交付的方法
  • TDengine 转化函数 TO_TIMESTAMP 用户手册
  • Python 程序设计讲义(21):循环结构——while循环
  • Leetcode力扣解题记录--第21题(合并链表)
  • C++ 常用的数据结构(适配器容量:栈、队列、优先队列)
  • [NPUCTF2020]ReadlezPHP
  • 基于深度学习的图像分类:使用Vision Transformer(ViT)实现高效分类
  • 【RDMA】Adapters PRM Mellanox Adapters Programmer’s Reference mellanox网卡编程手册0.52
  • Lua(数据库访问)
  • 【开发杂谈】用AI玩AI聊天游戏:使用 Electron 和 Python 开发大模型语音聊天软件
  • Web攻防-业务逻辑篇密码找回重定向目标响应包检验流程跳过回显泄露验证枚举
  • 前端核心进阶:从原理到手写Promise、防抖节流与深拷贝
  • OneCode3.0 Gallery 组件前后端映射机制:从注解配置到前端渲染的完整链路
  • [NLP]UPF+RTL联合仿真的VCS命令及UPF-aware 波形工具的使用
  • FPGA Verilog 入门语法指南
  • centos7安装docker命令
  • Scrapy
  • Qwen3-235B-A22B-Thinking-2507 - 开源思维推理模型的新标杆
  • 第二十天(正则表达式与功能实际运用)
  • VR 技术在污水处理领域的创新性应用探索​
  • STM32与ADS1220实现多通道数据采集的完整分析和源程序
  • 算法:数组part02: 209. 长度最小的子数组 +
  • SpringBoot整合Liquibase提升数据库变更的可控性、安全性、自动化程度(最详细)
  • 嵌入式学习-(李宏毅)机器学习(3)-day30
  • 图片查重从设计到实现(4)图片向量化存储-Milvus 单机版部署
  • Android悬浮窗导致其它应用黑屏问题解决办法
  • The Magic Mask for Android:解锁无限可能的安卓自定义套件
  • FT和RAG如何选择
  • win11 使用adb 获取安卓系统日志
  • freqtrade关于获取k线数量,以及显示时间的问题