C++ 条件变量,互斥锁

C++ 中多线程编程的两个核心同步原语:互斥锁 (Mutex)条件变量 (Condition Variable)。它们是实现线程间安全通信和协调的关键。

1. 互斥锁 (Mutex)

核心概念

互斥锁用于保护共享数据,确保同一时间只有一个线程可以访问该数据,从而避免数据竞争 (Data Race)

原理:线程在访问共享数据前先上锁 (lock),如果锁已被其他线程占用,则当前线程阻塞 (block) 等待。访问完成后解锁 (unlock),让其他线程有机会获取锁。

C++ 中的互斥锁 (<mutex> 头文件)

1. std::mutex

最基本的互斥锁。

cpp

#include <iostream>
#include <thread>
#include <mutex>std::mutex g_mutex; // 全局互斥锁
int shared_data = 0;void increment() {for (int i = 0; i < 100000; ++i) {g_mutex.lock();    // 上锁++shared_data;     // 临界区代码g_mutex.unlock();  // 解锁}
}int main() {std::thread t1(increment);std::thread t2(increment);t1.join();t2.join();std::cout << "Final value: " << shared_data << std::endl; // 正确输出 200000return 0;
}
2. std::lock_guard (推荐使用)

RAII 风格的锁管理,在构造时自动上锁,析构时自动解锁,即使发生异常也能保证解锁,避免死锁。

cpp

void safe_increment() {for (int i = 0; i < 100000; ++i) {std::lock_guard<std::mutex> lock(g_mutex); // 构造时上锁,析构时解锁++shared_data;} // lock_guard 在此析构,自动解锁
}
3. std::unique_lock (更灵活)

lock_guard 更灵活,可以手动控制上锁和解锁的时机,是条件变量必需的伙伴。

cpp

void flexible_increment() {for (int i = 0; i < 100000; ++i) {std::unique_lock<std::mutex> lock(g_mutex); // 自动上锁++shared_data;// 可以手动提前解锁,不需要等到作用域结束lock.unlock();// ... 这里可以执行一些不涉及共享数据的操作}
}

2. 条件变量 (Condition Variable)

核心概念

条件变量用于线程间的通信和协调。它允许一个线程等待某个条件成立,而其他线程在条件成立时通知等待的线程。

典型生产者-消费者模式

  • 消费者线程等待"缓冲区不为空"的条件

  • 生产者线程在放入数据后通知消费者

C++ 中的条件变量 (<condition_variable> 头文件)

基本使用模式

cpp

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>std::mutex mtx;
std::condition_variable cv;
std::queue<int> data_queue;
const int MAX_SIZE = 5;// 生产者线程
void producer() {for (int i = 0; i < 10; ++i) {std::unique_lock<std::mutex> lock(mtx);// 等待条件:队列未满// 使用lambda表达式作为等待条件cv.wait(lock, [] { return data_queue.size() < MAX_SIZE; });data_queue.push(i);std::cout << "Produced: " << i << std::endl;lock.unlock(); // 手动解锁(可选,notify之前解锁更好)cv.notify_one(); // 通知一个等待的消费者}
}// 消费者线程
void consumer() {while (true) {std::unique_lock<std::mutex> lock(mtx);// 等待条件:队列不为空cv.wait(lock, [] { return !data_queue.empty(); });int data = data_queue.front();data_queue.pop();std::cout << "Consumed: " << data << std::endl;lock.unlock();cv.notify_one(); // 通知生产者可能有空位了if (data == 9) break; // 收到最后一个数据后退出}
}int main() {std::thread prod(producer);std::thread cons(consumer);prod.join();cons.join();return 0;
}

条件变量的关键方法

  1. wait(lock, predicate):

    • 原子地解锁并阻塞当前线程

    • 被唤醒后重新获取锁

    • 检查 predicate 条件,如果为 false 则继续等待

  2. notify_one():

    • 唤醒一个等待中的线程(如果有)

  3. notify_all():

    • 唤醒所有等待中的线程


3. 为什么条件变量需要互斥锁?

条件变量必须与互斥锁配合使用,原因如下:

  1. 原子性操作:检查条件和进入等待必须是原子操作,否则可能发生:

    • 线程A检查条件 → 条件不满足

    • 线程B修改条件并发出通知

    • 线程A才开始等待 → 通知丢失,线程A永远等待

  2. 保护共享状态:条件变量等待的"条件"通常是共享数据,需要用互斥锁保护。


4. 完整的生产者-消费者示例

cpp

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>class ThreadSafeQueue {
private:std::queue<int> queue_;std::mutex mtx_;std::condition_variable cv_producer_;std::condition_variable cv_consumer_;const int max_size_ = 5;bool stop_ = false;public:void push(int value) {std::unique_lock<std::mutex> lock(mtx_);cv_producer_.wait(lock, [this] { return queue_.size() < max_size_ || stop_; });if (stop_) return;queue_.push(value);std::cout << "Produced: " << value << std::endl;cv_consumer_.notify_one();}int pop() {std::unique_lock<std::mutex> lock(mtx_);cv_consumer_.wait(lock, [this] { return !queue_.empty() || stop_; });if (stop_ && queue_.empty()) return -1;int value = queue_.front();queue_.pop();std::cout << "Consumed: " << value << std::endl;cv_producer_.notify_one();return value;}void stop() {std::lock_guard<std::mutex> lock(mtx_);stop_ = true;cv_producer_.notify_all();cv_consumer_.notify_all();}
};int main() {ThreadSafeQueue queue;std::thread producer([&queue] {for (int i = 0; i < 10; ++i) {queue.push(i);std::this_thread::sleep_for(std::chrono::milliseconds(100));}queue.stop();});std::thread consumer([&queue] {while (true) {int value = queue.pop();if (value == -1) break;std::this_thread::sleep_for(std::chrono::milliseconds(150));}});producer.join();consumer.join();return 0;
}

5. 重要注意事项和最佳实践

  1. 虚假唤醒 (Spurious Wakeup):线程可能在没有收到通知的情况下被唤醒,因此必须使用谓词检查条件。

  2. 优先使用 std::lock_guardstd::unique_lock:避免手动调用 lock()/unlock()

  3. 在通知前解锁:在调用 notify_one()notify_all() 前解锁,可以让被唤醒的线程立即获取锁,提高性能。

  4. 使用 RAII:确保异常安全,所有资源都能正确释放。

  5. 避免嵌套锁:容易导致死锁。

总结对比

特性互斥锁 (Mutex)条件变量 (Condition Variable)
主要目的保护共享数据,避免数据竞争线程间通信和协调
操作lock(), unlock()wait(), notify_one(), notify_all()
配合使用可以单独使用必须与互斥锁配合使用
阻塞原因等待获取锁等待某个条件成立
典型模式临界区保护生产者-消费者

掌握互斥锁和条件变量是编写正确、高效多线程 C++ 程序的基础。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/97763.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/97763.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MySQL 8.0 窗口函数详解:让数据分析更简单高效

在日常的数据分析工作中&#xff0c;我们经常需要对数据进行分组排序、计算移动平均值、统计累计求和等操作。在MySQL 8.0之前&#xff0c;这类需求通常需要编写复杂的子查询或连接查询才能实现。而MySQL 8.0引入的窗口函数&#xff08;Window Functions&#xff09;极大地简化…

【论文阅读】DeepSeek-LV2:用于高级多模态理解的专家混合视觉语言模型

【论文阅读】DeepSeek-LV2&#xff1a;用于高级多模态理解的专家混合视觉语言模型 文章目录【论文阅读】DeepSeek-LV2&#xff1a;用于高级多模态理解的专家混合视觉语言模型一、介绍二、模型结构三、数据建设**3.1 对齐****3.2 视觉语言预训练数据****3.3 监督微调数据**四、训…

一款为开发者而生的开源全栈LLMOps平台

&#x1f680; 超越ChatGPT&#xff01;一款为开发者而生的全栈LLMOps平台&#xff1a;LMForge完全指南 作为一名AI应用开发者&#xff0c;你是否也曾遇到过这些令人头疼的问题&#xff1f; 成本失控&#xff1a;GPT-4的API账单像雪片一样飞来&#xff0c;却不知道钱具体花在…

DeepL Translate在线工具测评:精准翻译技术文档与学术论文,支持多格式文档上传保留原格式

之前跟你们聊过帮着梳理代码协作的 GitLens&#xff0c;今天换个偏向文档翻译的方向 —— 给你们安利一个在线 AI 翻译工具「DeepL Translate」&#xff0c;官网地址是DeepL Translate: The worlds most accurate translator&#xff0c;它跟普通翻译工具不一样&#xff0c;翻技…

系统配置不是“乐高积木”:制造企业如何通过科学变更管理保障稳定运行

在制造业的数字化进程中&#xff0c;系统配置的稳定性常被忽视。作为一家制造企业的行政经理&#xff0c;我曾亲历这样的场景&#xff1a;为应对生产波动&#xff0c;各部门频繁要求调整ERP系统参数&#xff0c;结果导致库存数据失真、订单处理延迟&#xff0c;甚至引发客户投诉…

vscode炒股插件-韭菜盒子AI版

基于vscode插件&#xff0c;原韭菜盒子3.15.0版本开发&#xff0c;新增选股宝快讯功能、AI投资助手、指定股票AI分析功能&#xff08;目前只针对A股&#xff09;&#xff0c;内置AI大模型助手功能&#xff0c;支持ai分析最新资讯、ai分析当日资讯&#xff08;让ai随时给你分析股…

Spring Cloud Config 核心原理

Spring Cloud Config 是 Spring Cloud 提供的一个用于集中化管理应用程序各个环境下的配置属性的解决方案。它支持统一管理配置&#xff0c;并且可以在不重启应用的情况下动态地更新配置信息&#xff0c;提高开发和运维效率。 主要特点 • 集中管理配置&#xff1a;可以将不同环…

springboot ioc 控制反转入门与实战

Spring Boot3 IOC 项目地址https://gitee.com/supervol/loong-springboot-study&#xff08;记得给个start&#xff0c;感谢&#xff09;IOC 概述在 Spring Boot 3 中&#xff0c;IOC&#xff08;Inversion of Control&#xff0c;控制反转&#xff09;是核心思想之一&#xff…

LangGraph 重要注意事项和常见问题

01. 数据状态与归纳函数在前面的课时中&#xff0c;我们说过在 LangGraph 中 节点 在默认情况下返回的字典数据会将原始数据覆盖&#xff0c;例如下面的代码最终返回结果是 {"messages": [4]} 而不是 [1,2,3,4]&#xff0c;如下class MyState(TypedDict):messages: l…

避坑指南!解决Navicat运行SQL成功但没有表的问题

在运行转储的SQL文件时&#xff0c;成功运行&#xff0c;试了很多办法都不显示出表。原因&#xff1a;当从一个高版本的 MySQL 数据库导入数据到低版本的 MySQL 数据库时&#xff0c;可能会遇到兼容性问题。因为高版本的 MySQL 可能支持 utf8mb4_0900_ai_ci&#xff0c;而低版本…

在 Elasticsearch 中使用用户行为分析:使用 UBI 和 search-ui 创建一个应用程序

作者&#xff1a;来自 Elastic Eduard Martin 及 Alexander Dvila 通过一个实际示例学习如何在 Elasticsearch 中使用 UBI。我们将创建一个在搜索和点击结果时生成 UBI 事件的应用程序。 想要获得 Elastic 认证吗&#xff1f;看看下一次 Elasticsearch Engineer 培训什么时候开…

SpringBoot3中使用Caffeine缓存组件

SpringBoot3已经把EhCache从框架中删除了&#xff0c;SpringBoot3默认的缓存组件为Caffeine&#xff0c;那么我们在SpringBoot3中如何去使用它了&#xff1f; 1.添加依赖 <dependency><groupId>com.github.ben-manes.caffeine</groupId><artifactId>ca…

正则表达式与grep文本过滤详解

文章目录前言一、正则表达式概述1.1 定义1.2 主要用途1.3 Linux 中的正则表达式分类1.3.1 基础正则表达式&#xff08;BRE&#xff09;1.3.2 扩展正则表达式&#xff08;ERE&#xff09;二、正则表达式的基本组成2.1 普通字符2.2 元字符2.2.1 基本元字符2.2.2 重复次数相关2.2.…

Dify 集成 Milvus 配置指南

&#x1f9e9; Dify 集成 Milvus 配置指南 &#x1f527; 详细配置步骤 1. 环境准备与克隆仓库 首先确保你的系统已安装 Git、Docker 和 Docker Compose。然后克隆 Dify 的代码仓库&#xff1a; git clone https://github.com/langgenius/dify.git cd dify/docker2. 配置环境变…

为不平,不止于此

口碑可以成就一个人&#xff0c;也可以毁掉一个人&#xff0c; 所以我们选择用实力去创造两种无声的口碑。 要么让期待的你张口而呼&#xff0c; 要么让挑剔的你哑口无言。玛哈特科技创始人 #为不平&#xff0c;不止于此#

0902 C++类的匿名对象

Part 1.梳理思维导图一.匿名对象1.概念没有对象名的类对象2.格式类名();3.作用1.给有名对象初始化2.给对象数组初始化3.作为函数的参数传递给形参4.例子#include <iostream>using namespace std;class Dog {friend void Dogfriend(Dog &b); private:string name;int …

在 PySpark 中解锁窗口函数的力量,实现高级数据转换

本篇文章Mastering PySpark Window Functions: A Practical Guide to Time-Based Analytics适合数据分析和工程师入门了解PySpark的窗口函数。文章的亮点在于详细介绍了窗口函数的基本概念及其在销售数据分析中的实际应用&#xff0c;帮助读者理解如何进行复杂的数据计算而无需…

从理念到实践:三层解耦架构与“无系统”论

在上一篇中&#xff0c;我们揭示了“五层双闭环”治理模型如何像骨骼一样&#xff0c;为数字化转型提供支撑和定型。但再宏伟的蓝图也需要坚实的施工来实现。今天&#xff0c;我们将深入最具体的实施层面&#xff0c;将“业务重塑”和“以人为本”的理念&#xff0c;转化为可落…

详细介绍Linux 内存管理struct page数据结构中的_count和_mapcount有什么区别?

在Linux内核的struct page中&#xff0c;_count&#xff08;或_refcount&#xff09;和_mapcount是两个关键的引用计数成员&#xff0c;它们各自承担不同的职责。以下是深度解析和代码案例&#xff1a;1. _count vs _mapcount 区别详解_count&#xff08;或_refcount&#xff0…

面阵 vs 线阵相机:怎么选不踩坑?选型公式直接套用

面阵vs线阵相机&#xff1a;怎么选不踩坑&#xff1f;选型公式直接套用&#x1f3af;面阵vs线阵相机怎么选不踩坑&#xff1f;&#x1f3af;一、面阵相机&#xff1a;工业检测的“万能选手”&#xff0c;拍全图靠它&#x1f3af;二、线阵相机&#xff1a;大视野/高精度的“专属…