【C++】多线程同步三剑客介绍

目录

条件变量

头文件

主要操作函数

1、等待操作

2、唤醒操作

使用示例

信号量

头文件

主要操作函数

1、信号量初始化

2、等待操作(P操作)

3、信号操作(V操作)

4、获取信号量值 

5、销毁信号量

使用示例

互斥锁

头文件

使用示例


当我们需要给多个线程的指定执行顺序的时候,我们通常有多种方法:

  • 条件变量
  • 信号量
  • 互斥锁

在这篇文章里,会介绍如何使用这三种方式来为多个线程指定执行顺序,以及在使用的时候需要主义的地方。

条件变量

        条件变量是C++11引入的同步原语,用于在多线程环境中实现线程间的等待和通知机制。它允许一个或多个线程等待某个条件成立,当条件满足时,其他线程可以通知等待的线程继续执行,一般需要配合unique_lock使用。

头文件

#include <condition_variable>

主要操作函数

1、等待操作

a)基本形式

void wait(std::unique_lock<std::mutex>& lock);

b)带谓词形式

template<class Predicate>
void wait(std::unique_lock<std::mutex>& lock, Predicate pred);

 两者的区别在于处理虚假唤醒的情况比较明显,这个在后面介绍哈。

2、唤醒操作

a)唤醒单个线程

void notify_one() noexcept;

特点:唤醒等待队列中的一个线程,具体是哪个线程是未定义的

b)唤醒所有线程

void notify_all() noexcept;

特点:唤醒等待队列中的所有线程,性能开销比较大,但是确保所有等待线程都被唤醒。

这里需要介绍一下虚假唤醒的问题

        虚假唤醒是指线程在没有收到notify_one或者notify_all调用的情况,从wait状态中被唤醒。为什么会出现虚假唤醒的情况呢?因为可能会出现系统信号中断条件变量的等待(SIGINT),或者因为底层I/O操作等底层系统调用中断,导致pthread_cond_wait() 被中断返回,因此出现虚假唤醒的情况。

        在上面,我们介绍了两种等待的方式,他们在处理虚假唤醒的情况表现有所不同。

        带谓词的等待方式,会自动处理虚假唤醒,不需要我们再进行手动处理,那么他是怎么做到自动处理的呢,他的内部实现等价如下代码,就是在循环中不断判断条件是否满足,以此来处理虚假唤醒的情况。

// 带谓词的wait()函数的内部实现等价于:
template<class Predicate>
void wait(std::unique_lock<std::mutex>& lock, Predicate pred) {while (!pred()) {        // 关键:自动循环检查wait(lock);          // 调用基本的wait()}// 退出循环时,保证 pred() 返回 true
}

        基本的等待方式 需要我们手动处理虚假唤醒的情况,如下代码是有问题的:

void wrong_basic_wait() {std::unique_lock<std::mutex> lock(mtx);// 错误:只等待一次,不处理虚假唤醒cv.wait(lock);// 假设条件一定满足 - 危险!if (data_ready) {process_data();}
}

        如果因为底层系统调用中断了等待,但是此时条件并不满足,比如数据并未准备好,会出现未定义的情况,因此,我们需要模仿带谓词的等待方式的等价写法,在循环中判断,如下:

void correct_basic_wait() {std::unique_lock<std::mutex> lock(mtx);// 正确:使用循环处理虚假唤醒while (!condition_satisfied()) {cv.wait(lock);// 如果是虚假唤醒,循环会继续等待// 如果条件真的满足,循环会退出}// 这里保证条件一定满足process_data();
}

使用示例

1114. 按序打印 - 力扣(LeetCode)https://leetcode.cn/problems/print-in-order/description/

class Foo {condition_variable m_cv;mutex m_mtx;int m_nFlg;
public:Foo() {m_nFlg=1;}void first(function<void()> printFirst) {// printFirst() outputs "first". Do not change or remove this line.unique_lock<mutex> lock(m_mtx);m_cv.wait(lock,[=](){return m_nFlg==1;});printFirst();m_nFlg=2;m_cv.notify_all();}void second(function<void()> printSecond) {// printSecond() outputs "second". Do not change or remove this line.unique_lock<mutex> lock(m_mtx);m_cv.wait(lock,[=](){return m_nFlg==2;});printSecond();m_nFlg=3;m_cv.notify_all();}void third(function<void()> printThird) {// printThird() outputs "third". Do not change or remove this line.unique_lock<mutex> lock(m_mtx);m_cv.wait(lock,[=](){return m_nFlg==3;});printThird();m_nFlg=1;m_cv.notify_all();}
};

信号量

信号量的本质就是一个非负整数计数器,支持两个原子操作:P(等待/减少)、V(信号/增加)

头文件

#include <semaphore.h>

主要操作函数

1、信号量初始化

int sem_init(sem_t *sem, int pshared, unsigned int value);

参数说明:

  • sem:指向信号量(sem_t)的指针
  • pshared:0表示线程间共享,非0表示进程间共享
  • value:信号量的初始值

返回值

  • 返回0:初始化成功
  • 返回-1:初始化失败,同时设置errno的错误码。

2、等待操作(P操作)

信号量等待有三种方式

a)sem_wait()-阻塞等待

int sem_wait(sem_t *sem);

特点:如果信号量值为0,线程会一直阻塞等待,知道信号量可用。

b)sem_trywait()-非阻塞等待

int sem_trywait(sem_t *sem);

特点:非阻塞等待,立即返回,不会等待,如果信号量不可用,立即返回-1,不会造成线程阻塞的情况,适用于轮询场景

c)sem_timedwait() - 超时等待

int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);

特点:在指定时间内等待,超时后返回-1,使用绝对时间戳,不是相对时间。

这个参数比较多,这里演示下用法:

struct timespec结构体用于存储超时时间:

  • tv_sec:秒数
  • tv_nsec:纳秒数
#include <semaphore.h>
#include <iostream>
#include <time.h>
#include <errno.h>void timed_work() {struct timespec timeout;clock_gettime(CLOCK_REALTIME, &timeout);timeout.tv_sec += 5;  // 5秒后超时int result = sem_timedwait(&sem, &timeout);if (result == 0) {std::cout << "在超时前获取到信号量" << std::endl;// 执行临界区代码sem_post(&sem);} else {if (errno == ETIMEDOUT) {std::cout << "等待超时,放弃获取" << std::endl;}}
}

 下面的代码作用是获取当前的系统时间,CLOCK_REALTIME表示使用系统实时时钟

clock_gettime(CLOCK_REALTIME, &timeout);

3、信号操作(V操作)

释放信号量,也就是将信号量的值+1。

int sem_post(sem_t *sem);

4、获取信号量值 

int sem_getvalue(sem_t *sem, int *sval);

5、销毁信号量

这种只能用于未命名的信号量,比如我们直接定义的sem_t sem,就属于未命名信号量

int sem_destroy(sem_t *sem);

使用示例

1114. 按序打印 - 力扣(LeetCode)https://leetcode.cn/problems/print-in-order/description/这题希望我们指定三个线程的执行顺序,我们可以定义三个信号量来进行控制

class Foo {sem_t s1,s2,s3;
public:Foo() {sem_init(&s1,0,1);sem_init(&s2,0,0);sem_init(&s3,0,0);}~Foo() {sem_destroy(&s1);sem_destroy(&s2);sem_destroy(&s3);}void first(function<void()> printFirst) {// printFirst() outputs "first". Do not change or remove this line.sem_wait(&s1);printFirst();sem_post(&s2);}void second(function<void()> printSecond) {// printSecond() outputs "second". Do not change or remove this line.sem_wait(&s2);printSecond();sem_post(&s3);}void third(function<void()> printThird) {// printThird() outputs "third". Do not change or remove this line.sem_wait(&s3);printThird();sem_post(&s1);}
};

互斥锁

头文件

#include <mutex>

使用示例

因为互斥锁比较简单这里,直接展示使用示例:1114. 按序打印 - 力扣(LeetCode)https://leetcode.cn/problems/print-in-order/

class Foo {mutex mtx1,mtx2,mtx3;
public:Foo() {mtx2.lock();mtx3.lock();}void first(function<void()> printFirst) {// printFirst() outputs "first". Do not change or remove this line.mtx1.lock();printFirst();mtx2.unlock();}void second(function<void()> printSecond) {// printSecond() outputs "second". Do not change or remove this line.mtx2.lock();printSecond();mtx3.unlock();}void third(function<void()> printThird) {// printThird() outputs "third". Do not change or remove this line.mtx3.lock();printThird();mtx1.unlock();}
};

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

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

相关文章

《Java Web程序设计》实验报告八 JSP+Servlet+JDBC+MySQL实现课程管理

目 录 一、实验目的 二、实验环境 三、实验步骤和内容 1、小组成员分工&#xff08;共计4人&#xff09; 2、实验方案 3、实验结果与分析 4、项目任务评价 四、遇到的问题和解决方法 五、实验总结 一、实验目的 1、掌握mysql的安装、数据库表单创建 2、掌握JDBC的链接…

基于数据挖掘的课程推荐系统研究

摘要本研究设计并开发了一套基于先进数据挖掘技术的智能化课程推荐系统。该系统创新性地采用了协同过滤算法与内容推荐算法相结合的混合推荐策略&#xff0c;通过深度分析学生在学习平台上的历史行为数据&#xff08;包括选课记录、学习时长、测试成绩等&#xff09;以及课程的…

【SCI 4区推荐】《Journal of Visual Communication and Image Representation》

期刊简介&#xff1a;《视觉传达与图像表示杂志》&#xff08;Journal of Visual Communication and Image Representation&#xff09;致力于发表视觉传达与图像表示领域的最前沿研究&#xff0c;特别强调多学科交叉领域中的新技术和理论应用。这本期刊涵盖的研究范围广泛&…

20250711_Sudo 靶机复盘

target:192.168.43.20 外部打点 &#xff08;文件上传&#xff09; nmap扫一下&#xff0c;80,22 开放 扫目录&#xff0c;发现 README.md [17:04:30] 200 - 664B - /Dockerfile [17:04:38] 200 - 34KB - /LICENSE …

STEP 7-Micro/WIN SMART 编程软件:从入门到精通的使用指南

STEP 7-Micro/WIN SMART 编程软件&#xff1a;从入门到精通的使用指南 在工业自动化控制领域&#xff0c;编程软件是连接工程师与 PLC 的桥梁&#xff0c;而 STEP 7-Micro/WIN SMART 作为 S7-200 SMART PLC 的专用编程工具&#xff0c;以其友好的界面和高效的编程能力备受青睐。…

模型训练与部署注意事项篇---resize

图像大小的影响在 YOLOv 系列模型的训练和推理部署过程中&#xff0c;图像大小的选择是影响模型性能&#xff08;精度、速度、泛化能力&#xff09;的关键因素之一。两者的关系既相互关联&#xff0c;又存在一定的灵活性&#xff0c;具体可从以下几个方面详细分析&#xff1a;一…

【Python】venv:配置独立镜像源

为某个特定的 venv 虚拟环境设置 pip 镜像源&#xff0c;使得该环境下的 pip 安装始终使用自定义镜像源&#xff0c;不影响系统 pip&#xff0c;也不依赖用户级配置文件。环境准备 1. 创建虚拟环境 python -m venv venv2. 激活虚拟环境Windows: .\venv\Scripts\activateLinux/m…

日本语言学校:签证制度类 Prompt 的结构整理路径与策略

日本语言学校&#xff1a;签证制度类 Prompt 的结构整理路径与策略 我们在构建语言留学语义系统的过程中&#xff0c;尝试以“签证风险”为例&#xff0c;探索如何让结构信息被更好地保留下来。本文不介绍 Prompt 本身&#xff0c;也不夸大其作用&#xff0c;而是希望借此与更…

RFCOMM协议详解:串口仿真与TCP/IP协议栈移植技术——面试高频考点与真题解析

一、RFCOMM 协议核心考点与高频面试问题1.1 协议基础与核心功能考点解析&#xff1a;RFCOMM&#xff08;Radio Frequency Communication&#xff09;是蓝牙协议栈中实现串口仿真的核心协议&#xff0c;基于 L2CAP 协议提供类似 RS-232 的可靠数据流传输。其核心功能包括&#x…

【编程实践】利用open3d生成物体的最长边方向并可视化

1 利用3d软件生成一个长方体 边长随意&#xff0c;长度随意 2 导出为模型文件并采样为点云数据 从mesh表面进行采样&#xff0c;点数根据自己需求进行设置&#xff0c;此处设置为100000。采样结果&#xff1a;3 识别OBB外接框并可视化长边方向import numpy as np import open3d…

1. 好的设计原则

目录一、应该具备的性质二、面向对象设计原则三、详解3.1 开闭原则3.2 单一职责原则3.3 里氏替换原则3.4 依赖倒置原则3.5 接口隔离原则3.6 合成复用原则3.7 迪米特原则一、应该具备的性质 可扩展性灵活性可插入性 二、面向对象设计原则 以下设计原则的重要性从高到低排列 …

深度学习图像分类数据集—猫七种表情识别分类

该数据集为图像分类数据集&#xff0c;适用于ResNet、VGG等卷积神经网络&#xff0c;SENet、CBAM等注意力机制相关算法&#xff0c;Vision Transformer等Transformer相关算法。 数据集信息介绍&#xff1a;猫七种表情识别分类&#xff1a;[Angry, Disgusted, Happy, Normal, Sa…

002_Claude模型与定价

Claude模型与定价 目录 Claude 4 模型系列模型功能对比定价策略计费说明企业定价使用建议 Claude 4 模型系列 Anthropic 推出了最新的 Claude 4 系列模型&#xff0c;提供不同性能等级以满足各种需求&#xff1a; Claude Opus 4 定位&#xff1a;最强大、最智能的模型特点…

【牛客刷题】游游的字母串

文章目录 一、题目介绍1.1 题目描述1.2 输入描述:1.3 输出描述:1.4 示例1二、解题二、解题思路2.1 核心问题2.2 关键策略三、算法分析3.1 为什么正确?3.2 复杂度分析四、模拟演练五、完整代码一、题目介绍 题目:游游的字母串 1.1 题目描述 对于一个小写字母而言,游游可以通…

docker容器高级管理-dockerfile创建镜像

目录一.构建LNMP架构1.构建nginx容器①拉取centos镜像&#xff08;对镜像做基础架构&#xff09;②创建dockerfile工作目录&#xff08;可以是一个服务的项目&#xff09;③创建dockerfile④创建启动脚本2.构建mysql数据库①创建mysql项目②配置dockersfile文件④创建镜像文件3…

北京-4年功能测试2年空窗-报培训班学测开-第四十九天

今天自习&#xff0c;在自习室嘤…今天效率不高&#xff0c;导致焦虑。不&#xff0c;或者该说&#xff0c;因为焦虑导致效率不高&#xff1f;没有达到自己预期&#xff0c;对自己也不满意临近结课&#xff0c;突然有些迷茫&#xff0c;我知道我要做的还有很多&#xff0c;要学…

css选择器的优先级以及用法

在 CSS 中&#xff0c;当多个选择器同时作用于一个元素&#xff0c;并且为该元素的同一属性设置了不同的值时&#xff0c;就需要依据选择器的优先级来确定最终应用哪个样式。本文详细介绍 CSS 选择器优先级的相关内容。 本文目录一、单个选择器的优先级1. 内联样式2. ID 选择器…

hercules zos 安裝 jdk 8

首先到 IBM 官網下載相關 PAX 檔 (SDK8_31bit_SR8_FP45.PAX.Z) 和 SDK8_31bit_readme.txt https://www.ibm.com/support/pages/java-sdk-products-zos 在 hercules 裡加一些新卷用來存放這個 JDK (UAPP02)&#xff0c;UTMP02 也可以順便加上讓 OMVS IBMUSER 下多一些存放空間&…

张量索引操作

一.前言本期我们来说一下张量的索引操作&#xff0c;需要掌握张量不同索引操作&#xff0c;我们在操作张量时&#xff0c;经常需要去进⾏获取或者修改操作&#xff0c;掌握张量的花式索引操作是必须的⼀项能⼒。二.简单行、列索引import torchdata torch.randint(0, 10, [4, 5…

docker 启动中间件

docker 启动 MySQL # 创建目录 mkdir -p /Users/dongdong/software/mysql/{conf,data}docker run -d \ -p 3306:3306 \ -v /Users/dongdong/software/mysql/conf:/etc/mysql/conf.d \ -v /Users/dongdong/software/mysql/data:/var/lib/mysql \ -e MYSQL_ROOT_PASSWORDroot \ -…