C++线程同步之条件变量
C++线程同步之条件变量
文章目录
- C++线程同步之条件变量
- 什么是条件变量(Condition Variable)?
- 条件变量的主要用途
- 常见的应用场景
- C++11中的条件变量
- `condition_variable`的使用方法
- `std::condition_variable`的使用步骤
- 典型的使用示例:生产者-消费者模型
- `condition_variable_any`的使用方法
- `std::condition_variable_any`的使用步骤
- 典型的使用示例:生产者-消费者模型
- `condition_variable_any` vs `std::condition_variable`
- 注意事项
- 小结
什么是条件变量(Condition Variable)?
条件变量(condition_variable)是一种线程同步机制,用于在多线程程序中协调线程之间的执行顺序。它通过允许线程在某些条件成立时被唤醒,或者当某些条件不满足时进入等待状态,从而实现线程的协调和同步。与互斥锁(mutex)配合使用,条件变量允许线程在某个条件发生变化时继续执行。
条件变量的主要用途
条件变量的核心作用是:
- 线程等待某个条件:当一个线程需要等待某个特定条件发生时,它可以通过条件变量进入等待状态。比如等待队列中有元素时才能进行消费操作。
- 通知其他线程:当条件发生变化时,其他线程可以通知正在等待的线程继续执行。比如生产者线程生产了新的数据,通知消费者线程可以开始处理数据。
在并发编程中,条件变量通常与互斥锁一起使用,来确保数据访问的一致性。
常见的应用场景
- 生产者-消费者问题:多个生产者线程和多个消费者线程共享一个缓冲区,生产者将数据放入缓冲区,消费者从缓冲区取数据。在缓冲区为空时,消费者线程等待;在缓冲区已满时,生产者线程等待。条件变量可以用来协调生产者和消费者的执行顺序。
- 线程池:当线程池中的线程正在处理任务时,如果没有任务,线程会进入等待状态,直到任务被提交,线程才会被唤醒并执行任务。
- 线程之间的事件通知:比如一个线程执行一些初始化操作,另一个线程依赖于该操作完成后才能继续执行。通过条件变量,一个线程可以在另一个线程完成初始化后收到通知。
- 共享资源的访问:多个线程需要访问一个共享资源,在某些情况下资源可能不可用(比如池中没有空闲资源)。线程可以等待资源可用并在条件满足时继续执行。
C++11中的条件变量
在C++11中,标准库引入了条件变量类,提供了两种主要的条件变量:
std::condition_variable:这是最常用的条件变量,适用于需要在多个线程间协调的场景。它基于std::mutex或std::unique_lock<std::mutex>来进行线程同步。std::condition_variable_any:这是一个通用的条件变量,它可以与任意类型的锁一起使用(不仅限于std::mutex),但它的使用场景相对较少。
condition_variable的使用方法
std::condition_variable的使用步骤
- 创建一个条件变量。
- 使用
std::mutex或std::unique_lock来保护共享数据。 - 使用
wait函数使线程进入等待状态,直到接收到通知(notify_one或notify_all)。 - 使用
notify_one或notify_all通知一个或所有等待的线程。
典型的使用示例:生产者-消费者模型
#include <cstddef>
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <queue>
#include <thread>
#include <condition_variable>
#include <mutex>using namespace std;std::queue<int> buffer;
std::mutex mtx;
condition_variable m_QueueEmpty; // 队列为空的条件变量
condition_variable m_QueueFull; // 队列已满的条件变量const size_t max_buffer_size = 5;void producer() {std::unique_lock<std::mutex> lock(mtx);// 队列已满while (buffer.size() >= max_buffer_size) {cout << "buffer is full, producer wait..." << endl;m_QueueFull.wait(lock);}// 生产数据 模拟生产数据 随机获取1~100的随机数int data = rand() % 100 + 1;buffer.push(data);auto threadid = this_thread::get_id();printf("produce data: %d , thread id: %lld \n",data, threadid);// 通知消费者可以消费数据m_QueueEmpty.notify_one();
}void consumer() {unique_lock<mutex> lock(mtx);while (buffer.empty()) {cout << "buffer is empty, consumer wait..." << endl;m_QueueEmpty.wait(lock);}int data = buffer.front();buffer.pop();auto threadid = this_thread::get_id();printf("consume data: %d , thread id: %lld \n",data, threadid);// 通知生产者可以生产数据m_QueueFull.notify_one();
}int main() {thread t1[15];thread t2[15];for (int i = 0; i < 15; ++i){t1[i] = thread(producer);t2[i] = thread(consumer);}for (int i = 0; i < 15; ++i){t1[i].join();t2[i].join();}return 0;
}
条件变量condition_variable类的wait()还有一个重载的方法,可以接受一个条件,这个条件也可以是一个返回值为布尔类型的函数,条件变量会先检查判断这个条件是否满足,如果满足条件(布尔值为true),则当前线程重新获得互斥锁的所有权,结束阻塞,继续向下执行;如果不满足条件(布尔值为false),当前线程会释放互斥锁(解锁)同时被阻塞,等待被唤醒。
这样代码就可以优化一下:
void producer() {std::unique_lock<std::mutex> lock(mtx);// 队列已满m_QueueFull.wait(lock, []{ return buffer.size() < max_buffer_size; });// 生产数据 模拟生产数据 随机获取1~100的随机数int data = rand() % 100 + 1;buffer.push(data);auto threadid = this_thread::get_id();printf("produce data: %d , thread id: %lld \n",data, threadid);// 通知消费者可以消费数据m_QueueEmpty.notify_one();
}void consumer() {unique_lock<mutex> lock(mtx);m_QueueEmpty.wait(lock, []{ return !buffer.empty(); });int data = buffer.front();buffer.pop();auto threadid = this_thread::get_id();printf("consume data: %d , thread id: %lld \n",data, threadid);// 通知生产者可以生产数据m_QueueFull.notify_one();
}
修改之后可以发现,程序变得更加精简了,而且执行效率更高了,因为在这两个函数中的while循环被删掉了,但是最终的效果是一样的,推荐使用这种方式的wait()进行线程的阻塞。
condition_variable_any的使用方法
condition_variable_any 是 C++ 标准库中用于同步的一个条件变量,它允许线程在某些条件满足时继续执行。与标准的 std::condition_variable 相比,condition_variable_any 在底层实现上更加灵活,能够支持更多类型的锁,比如 std::mutex 和 std::shared_mutex,而 std::condition_variable 只能与 std::unique_lock<std::mutex> 配合使用。
std::condition_variable_any的使用步骤
std::condition_variable_any 的基本使用方式与 std::condition_variable 类似:
- 创建一个
condition_variable_any对象。 - 在某些条件下等待(
wait),当条件满足时,通知其他线程(notify_one或notify_all)。 - 使用合适的锁来保护共享数据。
典型的使用示例:生产者-消费者模型
#include <iostream>
#include <thread>
#include <condition_variable>
#include <mutex>
#include <queue>std::mutex mtx; // 互斥锁
std::condition_variable_any cv; // 条件变量
std::queue<int> buffer; // 模拟缓冲区
const int BUFFER_SIZE = 5; // 缓冲区最大容量// 生产者线程
void producer() {int product = 0;while (true) {std::this_thread::sleep_for(std::chrono::milliseconds(500)); // 模拟生产过程{std::lock_guard<std::mutex> lock(mtx);if (buffer.size() < BUFFER_SIZE) {buffer.push(product++);std::cout << "Produced: " << product << std::endl;cv.notify_one(); // 通知消费者有新的产品} else {std::cout << "Buffer is full. Producer is waiting." << std::endl;cv.wait(mtx, []{ return buffer.size() < BUFFER_SIZE; }); // 等待缓冲区有空间}}}
}// 消费者线程
void consumer() {while (true) {std::this_thread::sleep_for(std::chrono::milliseconds(1000)); // 模拟消费过程{std::lock_guard<std::mutex> lock(mtx);if (buffer.empty()) {std::cout << "Buffer is empty. Consumer is waiting." << std::endl;cv.wait(mtx, []{ return !buffer.empty(); }); // 等待缓冲区有数据} else {int product = buffer.front();buffer.pop();std::cout << "Consumed: " << product << std::endl;cv.notify_one(); // 通知生产者可以继续生产}}}
}int main() {std::thread producerThread(producer);std::thread consumerThread(consumer);producerThread.join();consumerThread.join();return 0;
}
- 生产者线程:生产者线程首先会检查缓冲区是否有空间,如果缓冲区已满,生产者将等待,直到缓冲区有空间。使用
cv.wait(mtx, predicate)来进行等待,其中predicate是一个 lambda 表达式,它会在缓冲区有空间时返回true,通知线程继续执行。 - 消费者线程:消费者线程首先会检查缓冲区是否为空。如果为空,消费者将等待,直到缓冲区有数据。使用同样的
cv.wait(mtx, predicate)来进行等待。 - 互斥锁 (
mtx):我们使用std::mutex来保证对缓冲区的互斥访问,确保线程安全。 - 条件变量的通知:当生产者生产了一个新产品或消费者消费了一个产品时,会通过
cv.notify_one()来通知另一个线程继续执行。
condition_variable_any vs std::condition_variable
std::condition_variable_any的主要优势在于,它不局限于与std::mutex一起使用,还可以与其他类型的锁配合使用,比如std::shared_mutex。- 在大多数情况下,
std::condition_variable_any和std::condition_variable用法类似。如果你不需要特别灵活的锁类型,std::condition_variable更常见。
注意事项
- 避免虚假唤醒:
cv.wait()会在被唤醒后重新检查条件,因此要使用cv.wait(mtx, predicate)这种带条件的等待方式,而不是简单地调用cv.wait(mtx),以防止虚假唤醒。 - 使用
notify_one()或notify_all():通常我们使用notify_one()来唤醒一个等待线程,或者使用notify_all()来唤醒所有等待线程。在生产者-消费者模型中,notify_one()是常用的选择,因为每次只有一个线程被唤醒。
小结
condition_variable_any提供了一个灵活的方式来实现线程间的同步,尤其是对于更复杂的锁类型(如std::shared_mutex)。- 在生产者-消费者模型中,它有助于实现线程间的协调,使得生产者在缓冲区满时等待,消费者在缓冲区空时等待,通过适时的通知机制有效地协调生产和消费过程。
相关文章:
C++线程同步之条件变量
C线程同步之条件变量 文章目录 C线程同步之条件变量什么是条件变量(Condition Variable)?条件变量的主要用途常见的应用场景C11中的条件变量condition_variable的使用方法std::condition_variable的使用步骤典型的使用示例:生产者…...
如何实现多条件搜索
我们先来看多条件查询的样式是什么样的! 给查询按钮添加点击事件,然后获取到对应输入框中的值 然后通过filter过滤,对获取到的数据进行筛选 ,然后调用渲染函数将过滤搜索到的数据在页面中显示出来。 这就是进行多条件搜索出来的效…...
深入MySQL复杂查询优化技巧
在上一篇文章中,我们介绍了 MySQL 的关联关系理论与基础实践。本篇文章将进一步探讨 MySQL 复杂查询的优化技巧,帮助开发者应对大型数据集和高并发场景中的性能挑战。我们将涵盖索引设计、查询计划分析、分区技术以及事务管理的优化。 一、索引优化 索引…...
Fabric环境部署-Git和Node安装
一.安装Git(v2.43.0) Git 是一个开源的分布式版本管理系统(也是全球最大的开源软件存储服务器),用于敏捷高效地处理任何或小或大的项目。搭建区块链需要使用Git,因为区块链的开发和部署需要使用版本控制工…...
如何弥补开源大语言模型解决推理任务的不足
在实际应用中,大语言模型(LLM)可以通过与其他专门的推理技术结合,克服其在严格逻辑推理、深度推理或因果推理领域的不足。以下是几种有效的结合方式,分别从不同角度解决LLM在推理中的局限性。 一、结合符号推理系统 …...
Ubuntu 下载安装 Consul1.17.1
下载 wget https://releases.hashicorp.com/consul/1.17.1/consul_1.17.1_linux_amd64.zip解压: unzip -d consul_1.17.1_linux_amd64.zip /opt/module将解压出的二进制文件移动到 /usr/local/bin 目录中以便在系统中全局使用: sudo mv consul /usr/l…...
【数据库系统概论】并发控制--复习
1. 并发控制概述 并发控制是数据库系统处理多个事务同时执行时,保证数据一致性和事务隔离性的关键技术。 1.1并发操作的特点 数据库系统允许多个用户并发访问。典型应用场景: 飞机订票系统银行数据库系统网上购物系统 1.2并发操作可能带来的问题 并…...
MySQL(六)MySQL 案例
1. MySQL 案例 1.1. 设计数据库 1、首先根据相关业务需求(主要参考输出输入条件)规划出表的基本结构 2、根据业务规则进行状态字段设计 3、预估相关表的数据量进行容量规划 4、确定主键 5、根据对相关处理语句的分析对数据结构进行相应的变更。 设计表的时…...
DDcGAN_多分辨率图像融合的双鉴别条件生成对抗网络_y译文马佳义
摘要: 在本文中,我们提出了一种新的端到端模型,称为双鉴别条件生成对抗网络(DDcGAN),用于融合不同分辨率的红外和可见光图像。我们的方法建立了一个生成器和两个鉴别器之间的对抗博弈。生成器的目的是基于特…...
[读书日志]从零开始学习Chisel 第一篇:书籍介绍,Scala与Chisel概述,Scala安装运行(敏捷硬件开发语言Chisel与数字系统设计)
简介:从20世纪90年代开始,利用硬件描述语言和综合技术设计实现复杂数字系统的方法已经在集成电路设计领域得到普及。随着集成电路集成度的不断提高,传统硬件描述语言和设计方法的开发效率低下的问题越来越明显。近年来逐渐崭露头角的敏捷化设…...
二、用例图
二、用例图 (一)、用例图的基本概念 1、用例图的定义: 用例图是表示一个系统中用例与参与者关系之间的图。它描述了系统中相关的用户和系统对不同用户提供的功能和服务。 用例图相当于从用户的视角来描述和建模整个系统,分析系统的功能与…...
LWIP之一:使用STM32CubeMX搭建基于FreeRTOS的LWIP工程并分析协议栈初始化过程
工程搭建及LWIP协议栈初始化过程 一、使用STM32CubeMX快速生成工程二、修改测试三、LWIP协议栈初始化过程分析3.1 tcpip_init()3.1.1 lwip_init()3.1.1.1 sys_init()3.1.1.2 mem_init()3.1.1.3 memp_init()3.1.1.4 netif_init()3.1.1.5 udp_init()3.1.1.6 tcp_init()3.1.1.7 ig…...
个性化电影推荐系统|Java|SSM|JSP|
【技术栈】 1⃣️:架构: B/S、MVC 2⃣️:系统环境:Windowsh/Mac 3⃣️:开发环境:IDEA、JDK1.8、Maven、Mysql5.7 4⃣️:技术栈:Java、Mysql、SSM、Mybatis-Plus、JSP、jquery,html 5⃣️数据库可…...
UE5AI感知组件
官方解释: AI感知系统为Pawn提供了一种从环境中接收数据的方式,例如噪音的来源、AI是否遭到破坏、或AI是否看到了什么。 AI感知组件(AIPerception Component)是用于实现游戏中的非玩家角色(NPC)对环境和其…...
每日一学——日志管理工具(ELK Stack)
5.1 ELK Stack 5.1.1 Elasticsearch索引机制 嘿,小伙伴们!今天我们要聊聊ELK Stack——一套由Elasticsearch、Logstash和Kibana组成的强大日志管理工具集。通过这套工具,我们可以轻松地收集、存储、搜索和可视化日志数据。首先,…...
“智能筛查新助手:AI智能筛查分析软件系统如何改变我们的生活
嘿,朋友们!今天咱们来聊聊一个特别厉害的工具——AI智能筛查分析软件系统。想象一下,如果你有一个超级聪明的小助手,不仅能帮你快速找出问题的关键所在,还能提供精准的解决方案,是不是感觉工作和生活都变得…...
DeepSeek v3为何爆火?如何用其集成Milvus搭建RAG?
最近,DeepSeek v3(一个MoE模型,拥有671B参数,其中37B参数被激活)模型全球爆火。 作为一款能与Claude 3.5 Sonnet,GPT-4o等模型匹敌的开源模型DeepSeek v3不仅将其算法开源,还放出一份扎实的技术…...
linux-centos-安装miniconda3
参考: 最新保姆级Linux下安装与使用conda:从下载配置到使用全流程_linux conda-CSDN博客 https://blog.csdn.net/qq_51566832/article/details/144113661 Linux上删除Anaconda或Miniconda的步骤_linux 删除anaconda-CSDN博客 https://blog.csdn.net/m0_…...
html+css+js网页设计 美食 好厨艺西餐美食企业网站模板6个页面
htmlcssjs网页设计 美食 好厨艺西餐美食企业网站模板6个页面 网页作品代码简单,可使用任意HTML辑软件(如:Dreamweaver、HBuilder、Vscode 、Sublime 、Webstorm、Text 、Notepad 等任意html编辑软件进行运行及修改编辑等操作)。 …...
QT-窗口嵌入外部exe
窗口类: #pragma once #include <QApplication> #include <QWidget> #include <QVBoxLayout> #include <QProcess> #include <QTimer> #include <QDebug> #include <Windows.h> #include <QWindow> #include <…...
生成xcframework
打包 XCFramework 的方法 XCFramework 是苹果推出的一种多平台二进制分发格式,可以包含多个架构和平台的代码。打包 XCFramework 通常用于分发库或框架。 使用 Xcode 命令行工具打包 通过 xcodebuild 命令可以打包 XCFramework。确保项目已经配置好需要支持的平台…...
day52 ResNet18 CBAM
在深度学习的旅程中,我们不断探索如何提升模型的性能。今天,我将分享我在 ResNet18 模型中插入 CBAM(Convolutional Block Attention Module)模块,并采用分阶段微调策略的实践过程。通过这个过程,我不仅提升…...
以下是对华为 HarmonyOS NETX 5属性动画(ArkTS)文档的结构化整理,通过层级标题、表格和代码块提升可读性:
一、属性动画概述NETX 作用:实现组件通用属性的渐变过渡效果,提升用户体验。支持属性:width、height、backgroundColor、opacity、scale、rotate、translate等。注意事项: 布局类属性(如宽高)变化时&#…...
3.3.1_1 检错编码(奇偶校验码)
从这节课开始,我们会探讨数据链路层的差错控制功能,差错控制功能的主要目标是要发现并且解决一个帧内部的位错误,我们需要使用特殊的编码技术去发现帧内部的位错误,当我们发现位错误之后,通常来说有两种解决方案。第一…...
解锁数据库简洁之道:FastAPI与SQLModel实战指南
在构建现代Web应用程序时,与数据库的交互无疑是核心环节。虽然传统的数据库操作方式(如直接编写SQL语句与psycopg2交互)赋予了我们精细的控制权,但在面对日益复杂的业务逻辑和快速迭代的需求时,这种方式的开发效率和可…...
Go 语言接口详解
Go 语言接口详解 核心概念 接口定义 在 Go 语言中,接口是一种抽象类型,它定义了一组方法的集合: // 定义接口 type Shape interface {Area() float64Perimeter() float64 } 接口实现 Go 接口的实现是隐式的: // 矩形结构体…...
Objective-C常用命名规范总结
【OC】常用命名规范总结 文章目录 【OC】常用命名规范总结1.类名(Class Name)2.协议名(Protocol Name)3.方法名(Method Name)4.属性名(Property Name)5.局部变量/实例变量(Local / Instance Variables&…...
深入理解JavaScript设计模式之单例模式
目录 什么是单例模式为什么需要单例模式常见应用场景包括 单例模式实现透明单例模式实现不透明单例模式用代理实现单例模式javaScript中的单例模式使用命名空间使用闭包封装私有变量 惰性单例通用的惰性单例 结语 什么是单例模式 单例模式(Singleton Pattern&#…...
vue3 字体颜色设置的多种方式
在Vue 3中设置字体颜色可以通过多种方式实现,这取决于你是想在组件内部直接设置,还是在CSS/SCSS/LESS等样式文件中定义。以下是几种常见的方法: 1. 内联样式 你可以直接在模板中使用style绑定来设置字体颜色。 <template><div :s…...
MFC 抛体运动模拟:常见问题解决与界面美化
在 MFC 中开发抛体运动模拟程序时,我们常遇到 轨迹残留、无效刷新、视觉单调、物理逻辑瑕疵 等问题。本文将针对这些痛点,详细解析原因并提供解决方案,同时兼顾界面美化,让模拟效果更专业、更高效。 问题一:历史轨迹与小球残影残留 现象 小球运动后,历史位置的 “残影”…...
