『 C++ 』多线程同步:条件变量及其接口的应用实践

文章目录

  • 条件变量概述
    • 条件变量简介
    • 条件变量的基本用法
  • 案例:两个线程交替打印奇偶数
    • 代码解释
  • `std::unique_lock::try_lock_until` 介绍
    • 代码示例
    • 代码解释
    • 注意事项
  • `std::condition_variable::wait` 详细解析与示例
    • `std::condition_variable::wait` 接口介绍
    • 代码示例
    • 代码解释
    • 关于最后一个互斥锁可能再次阻塞线程
  • 条件变量其他接口的应用示例
    • `wait_for` 接口应用
    • `wait_until` 接口应用
    • `notify_one` 接口应用
    • `notify_all` 接口应用

条件变量概述

在 C++ 多线程编程里,同步机制是极为关键的部分,它能保证多个线程安全且高效地访问共享资源。其中,条件变量(std::condition_variable)和 std::unique_lock::try_lock_until 是很实用的工具。接下来,我们会深入探讨它们的应用。

条件变量简介

条件变量是 C++ 标准库中的一个同步原语,它可让线程在特定条件达成时被唤醒。其主要用途是线程间的等待 - 通知机制,一个线程等待某个条件成立,而另一个线程在条件成立时通知等待的线程。

条件变量的基本用法

条件变量的基本操作包含 waitwait_forwait_untilnotify_one/notify_all

  • wait:使线程进入等待状态,同时释放互斥量,直到被其他线程唤醒。
  • wait_for:线程会等待一段时间,若在这段时间内被通知则继续执行,若超时则继续执行。
  • wait_until:线程会等待到指定的时间点,若在该时间点前被通知则继续执行,若到达时间点还未被通知则继续执行。
  • notify_one:唤醒一个正在等待的线程。
  • notify_all:唤醒所有正在等待的线程。

案例:两个线程交替打印奇偶数

以下是一个使用条件变量实现两个线程交替打印奇偶数的案例:

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>using namespace std;
void two_thread_print()
{std::mutex mtx;condition_variable c;int n = 100;bool flag = true;thread t1([&]() {int i = 0;while (i < n){unique_lock<mutex> lock(mtx);c.wait(lock, [&]()->bool {return flag; });cout << i << endl;flag = false;i += 2; // 偶数c.notify_one();}});thread t2([&]() {int j = 1;while (j < n){unique_lock<mutex> lock(mtx);c.wait(lock, [&]()->bool {return !flag; });cout << j << endl;flag = true;j += 2; // 奇数c.notify_one();}});t1.join();t2.join();
}int main()
{two_thread_print();return 0;
}    

代码解释

  1. 线程函数
    • t1 线程负责打印偶数,它会等待 flagtrue 时开始打印,打印完成后将 flag 置为 false,并通知另一个线程。
    • t2 线程负责打印奇数,它会等待 flagfalse 时开始打印,打印完成后将 flag 置为 true,并通知另一个线程。
  2. 条件变量的使用
    • c.wait(lock, [&]()->bool {return flag; });c.wait(lock, [&]()->bool {return !flag; }); 用于线程的等待,当条件不满足时线程会进入等待状态,同时释放互斥量。
    • c.notify_one(); 用于唤醒另一个等待的线程。
  3. 线程同步
    • 通过 t1.join()t2.join() 确保主线程等待两个子线程执行完毕。

std::unique_lock::try_lock_until 介绍

std::unique_lock::try_lock_until 是 C++ 标准库 <mutex> 头文件中的一部分,用于尝试在指定的时间点之前锁定关联的互斥量。如果在指定时间之前成功锁定,它会返回 true;若超时仍未锁定,则返回 false

代码示例

下面的代码模拟了一个多线程场景,主线程会尝试在指定时间内锁定互斥量,而工作线程会先占用一段时间互斥量。

#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>std::mutex mtx;void worker() {std::this_thread::sleep_for(std::chrono::seconds(2));std::lock_guard<std::mutex> lock(mtx);std::cout << "Worker thread locked the mutex." << std::endl;std::this_thread::sleep_for(std::chrono::seconds(2));std::cout << "Worker thread unlocked the mutex." << std::endl;
}int main() {std::thread t(worker);std::unique_lock<std::mutex> lock(mtx, std::defer_lock);auto timeout = std::chrono::steady_clock::now() + std::chrono::seconds(1);if (lock.try_lock_until(timeout)) {std::cout << "Main thread locked the mutex." << std::endl;lock.unlock();} else {std::cout << "Main thread failed to lock the mutex within the timeout." << std::endl;}t.join();return 0;
}    

代码解释

  1. 线程函数 worker:工作线程会先休眠 2 秒,然后锁定互斥量 mtx,打印信息,再休眠 2 秒,最后解锁互斥量。
  2. 主线程逻辑
    • 创建工作线程 t
    • 创建 std::unique_lock 对象 lock,初始时不锁定互斥量。
    • 设定超时时间为当前时间加上 1 秒。
    • 调用 try_lock_until 尝试在超时时间之前锁定互斥量。
    • 根据返回结果输出相应信息。
  3. 线程同步:主线程通过 t.join() 等待工作线程结束。

注意事项

  • 此函数适用于需要在一定时间内尝试锁定互斥量的场景,避免无限期等待。
  • 超时时间点可以使用不同的时钟类型和时间单位,如 std::chrono::steady_clockstd::chrono::system_clock

std::condition_variable::wait 详细解析与示例

std::condition_variable::wait 接口介绍

std::condition_variable::wait 有两种重载形式:

// 无条件形式
void wait (unique_lock<mutex>& lck);
// 带谓词形式
template <class Predicate>  
void wait (unique_lock<mutex>& lck, Predicate pred);

当前线程(应已锁定互斥锁)的执行将被阻止,直到收到通知。在阻塞线程的那一刻,该函数会自动调用 lck.unlock(),允许其他锁定的线程继续。一旦收到通知(显式地,由其他线程通知),该函数就会解除阻塞并调用 lck.lock(),离开时的状态与调用函数时的状态相同。然后函数返回(请注意,最后一个互斥锁可能会在返回之前再次阻塞线程)。

通常,通过在另一个线程中调用 notify_onenotify_all 来通知函数唤醒。但某些实现可能会在不调用这些函数的情况下产生虚假的唤醒调用。因此,此功能的用户应确保满足其恢复条件。

如果指定了带谓词的版本(2),则该函数仅在 pred() 返回 false 时阻塞,并且通知只能在线程变为 pred() 返回 true 时取消阻塞线程(这对于检查虚假唤醒调用特别有用)。

代码示例

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>std::mutex mtx;
std::condition_variable cv;
bool ready = false;void worker() {std::unique_lock<std::mutex> lock(mtx);// 使用带谓词的 wait 版本cv.wait(lock, []{ return ready; });std::cout << "Worker thread is working." << std::endl;
}int main() {std::thread t(worker);{std::lock_guard<std::mutex> lock(mtx);ready = true;}cv.notify_one();t.join();return 0;
}

代码解释

  1. worker 线程worker 线程获取 unique_lock 并调用 cv.wait(lock, []{ return ready; });,如果 readyfalse,线程会进入等待状态,同时释放互斥锁。当收到通知并且 ready 变为 true 时,线程会重新获取互斥锁并继续执行。
  2. 主线程:主线程设置 readytrue 并调用 cv.notify_one() 通知等待的线程。

关于最后一个互斥锁可能再次阻塞线程

cv.wait(lock, []{ return ready; }); 中,当收到通知且谓词 []{ return ready; } 返回 true 时,wait 函数会尝试重新锁定互斥锁(调用 lck.lock())。如果此时互斥锁被其他线程持有,当前线程会再次被阻塞,直到成功获取互斥锁。

条件变量其他接口的应用示例

wait_for 接口应用

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <chrono>std::mutex mtx;
std::condition_variable cv;
bool ready = false;void worker() {std::unique_lock<std::mutex> lock(mtx);if (cv.wait_for(lock, std::chrono::seconds(2), []{ return ready; })) {std::cout << "Worker thread got notified in time." << std::endl;} else {std::cout << "Worker thread timed out." << std::endl;}
}int main() {std::thread t(worker);std::this_thread::sleep_for(std::chrono::seconds(3));{std::lock_guard<std::mutex> lock(mtx);ready = true;}cv.notify_one();t.join();return 0;
}

这里 worker 线程会等待 2 秒,如果在这 2 秒内没有收到通知,就会超时继续执行。

wait_until 接口应用

#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>std::timed_mutex mtx;
bool ready = false;void worker() {std::this_thread::sleep_for(std::chrono::seconds(2));std::lock_guard<std::timed_mutex> lock(mtx); // 使用 std::timed_mutexstd::cout << "Worker thread locked the mutex." << std::endl;std::this_thread::sleep_for(std::chrono::seconds(2));ready = true;std::cout << "Worker thread unlocked the mutex." << std::endl;
}int main() {std::thread t(worker);std::unique_lock<std::timed_mutex> lock(mtx, std::defer_lock); // 使用 std::timed_mutexauto timeout = std::chrono::system_clock::now() + std::chrono::seconds(1);if (lock.try_lock_until(timeout)) { // 正确:std::timed_mutex 支持 try_lock_untilstd::cout << "Main thread locked the mutex." << std::endl;lock.unlock();}else {std::cout << "Main thread failed to lock the mutex within the timeout." << std::endl;}t.join();return 0;
}

worker 线程会等待到指定的时间点,如果在该时间点前收到通知则继续执行,否则超时继续执行。

notify_one 接口应用

参考前面两个线程交替打印奇偶数的例子,c.notify_one(); 用于唤醒另一个等待的线程。

notify_all 接口应用

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <vector>std::mutex mtx;
std::condition_variable cv;
bool ready = false;void worker(int id) {std::unique_lock<std::mutex> lock(mtx);cv.wait(lock, []{ return ready; });std::cout << "Worker thread " << id << " is working." << std::endl;
}int main() {std::vector<std::thread> threads;for (int i = 0; i < 3; ++i) {threads.emplace_back(worker, i);}{std::lock_guard<std::mutex> lock(mtx);ready = true;}cv.notify_all();for (auto& t : threads) {t.join();}return 0;
}

在这个例子中,notify_all 会唤醒所有等待的线程。

通过条件变量和 std::unique_lock::try_lock_until,我们能够更灵活地实现多线程同步,避免死锁和资源竞争问题。在实际开发中,根据具体需求合理运用这些工具,可以提高程序的性能和稳定性。

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

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

相关文章

【VolView】纯前端实现CT三维重建-CBCT

文章目录 什么是CBCTCBCT技术路线使用第三方工具使用Python实现使用前端实现 纯前端实现方案优缺点使用VolView实现CBCT VolView的使用1.克隆代码2.配置依赖3.运行4.效果 进阶&#xff1a;VolView配合Python解决卡顿1.修改VtkThreeView.vue2.新增Custom3DView.vue3.Python生成s…

debug - 安装.msi时,为所有用户安装程序

文章目录 debug - 安装.msi时&#xff0c;为所有用户安装程序概述笔记试试在目标.msi后面直接加参数的测试 备注备注END debug - 安装.msi时&#xff0c;为所有用户安装程序 概述 为了测试&#xff0c;装了一个test.msi. 安装时&#xff0c;只有安装路径的选择&#xff0c;没…

Java Stream两种list判断字符串是否存在方案

这里写自定义目录标题 背景初始化方法一、filter过滤方法二、anyMatch匹配 背景 在项目开发中&#xff0c;经常遇到筛选list中是否包含某个子字符串&#xff0c;有多种方式&#xff0c;本篇主要介绍stream流的filter和anyMatch两种方案&#xff0c;记录下来&#xff0c;方便备…

DeepSeek vs 通义大模型:谁将主导中国AI的未来战场?

当你在深夜调试代码时,是否幻想过AI伙伴能真正理解你的需求?当企业面对海量数据时,是否期待一个真正智能的决策大脑? 这场由DeepSeek和通义领衔的大模型之争,正在重塑中国AI产业的竞争格局。本文将为你揭开两大技术巨头的终极对决! 一、颠覆认知的技术突破 1.1 改变游戏…

3. 轴指令(omron 机器自动化控制器)——>MC_SetOverride

机器自动化控制器——第三章 轴指令 12 MC_SetOverride变量▶输入变量▶输出变量▶输入输出变量 功能说明▶时序图▶重启运动指令▶多重启动运动指令▶异常 MC_SetOverride 变更轴的目标速度。 指令名称FB/FUN图形表现ST表现MC_SetOverride超调值设定FBMC_SetOverride_instan…

从像素到世界:自动驾驶视觉感知的坐标变换体系

接着上一篇 如何让自动驾驶汽车“看清”世界?坐标映射与数据融合详解的概述,这一篇详细讲解自动驾驶多目视觉系统设计原理,并给出应用示例。 摘要 在自动驾驶系统中,准确的环境感知是实现路径规划与决策控制的基础。本文系统性地解析图像坐标系、像素坐标系、相机坐标系与…

附录B ISO15118-20测试命令

本章节给出ISO15118-20协议集的V2G命令&#xff0c;包含json、xml&#xff0c;并且根据exiCodec.jar编码得到exi内容&#xff0c; 读者可以参考使用&#xff0c;测试编解码库是否能正确编解码。 B.1 supportedAppProtocolReq json: {"supportedAppProtocolReq": {…

VLAN章节学习

为什么会有vlan这个技术&#xff1f; 1.通过划分广播域来降低广播风暴导致的设备性能下降&#xff1b; 2.提高网络管理的灵活性和通过隔离网络带来的安全性&#xff1b; 3.在成本不变的情况下增加更多的功能性&#xff1b; VLAN又称虚拟局域网&#xff08;再此扩展&#xf…

FPGA时钟约束

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 目录 前言 一、Create_clock 前言 时钟周期约束&#xff0c;就是对时钟进行约束。 一、Create_clock create_clock -name <name> -period <period> -waveform …

机房布局和布线的最佳实践:如何打造高效、安全的机房环境

机房布局和布线的最佳实践:如何打造高效、安全的机房环境 大家好,我是Echo_Wish。今天我们来聊聊机房布局和布线的问题,这可是数据中心和IT运维中的一个非常重要的环节。不管是刚刚接触运维的新人,还是已经摸爬滚打多年的老兵,都应该对机房的布局和布线有一个清晰的认识。…

spring-security原理与应用系列:建造者

目录 1.构建过程 AbstractSecurityBuilder AbstractConfiguredSecurityBuilder WebSecurity 2.建造者类图 SecurityBuilder ​​​​​​​AbstractSecurityBuilder ​​​​​​​AbstractConfiguredSecurityBuilder ​​​​​​​WebSecurity 3.小结 紧接上一篇文…

OpenHarmony子系统开发 - 电池管理(二)

OpenHarmony子系统开发 - 电池管理&#xff08;二&#xff09; 五、充电限流限压定制开发指导 概述 简介 OpenHarmony默认提供了充电限流限压的特性。在对终端设备进行充电时&#xff0c;由于环境影响&#xff0c;可能会导致电池温度过高&#xff0c;因此需要对充电电流或电…

xy轴不等比缩放问题——AUTOCAD c#二次开发

在 AutoCAD .net api里&#xff0c;部分实体&#xff0c;像文字、属性、插入块等&#xff0c;是不支持非等比缩放的。 如需对AutoCAD中图形进行xyz方向不等比缩放&#xff0c;则需进行额外的函数封装。 选择图元&#xff0c;指定缩放基准点&#xff0c;scaleX 0.5, scaleY …

如何在 HTML 中创建一个有序列表和无序列表,它们的语义有何不同?

大白话如何在 HTML 中创建一个有序列表和无序列表&#xff0c;它们的语义有何不同&#xff1f; 1. HTML 中有序列表和无序列表的基本概念 在 HTML 里&#xff0c;列表是一种用来组织信息的方式。有序列表就是带有编号的列表&#xff0c;它可以让内容按照一定的顺序呈现&#…

kafka的文章

1.面试的问题 要点 至多一次、恰好一次数据一致性超时重试、幂等消息顺序消息挤压延时消息 1.1 kafaka 生产消息的过程。 在消息发送的过程中&#xff0c;涉及到了两个线程&#xff0c;一个是main 线程&#xff0c;一个是sender 线程。在main 线程中创建了一个双端队列 Reco…

以mysql 为例,增删改查语法及其他高级特性

以下是 MySQL 的 增删改查语法及 高级特性的详细整理&#xff0c;结合示例说明&#xff1a; 1. 基础操作&#xff08;CRUD&#xff09; (1) 创建数据&#xff08;INSERT&#xff09; -- 单条插入 INSERT INTO users (id, name, email) VALUES (1, Alice, aliceexample.com);…

Postman最新详细安装及使用教程【附安装包】

一、Postman介绍 ‌Postman是一个功能强大的API测试工具&#xff0c;主要用于模拟和测试各种HTTP请求&#xff0c;支持GET、POST、PUT、DELETE等多种请求方法。‌通过Postman&#xff0c;用户可以发送请求并查看返回的响应&#xff0c;检查响应的内容和状态&#xff0c;从而验…

第十三章 : Names in Templates_《C++ Templates》notes

Names in Templates 重难点多选题设计题 重难点 1. 名称分类与基本概念 知识点&#xff1a; 限定名&#xff08;Qualified Name&#xff09;&#xff1a;使用::或.显式指定作用域的名称&#xff08;如std::vector&#xff09;非限定名&#xff08;Unqualified Name&#xff0…

整合vue+Element UI 开发管理系统

1、 安装 Node.js 和 npm 确保安装了 Node.js 和 npm。可以通过 Node.js 官网 下载。 2、 创建 Vue 项目 安装cli npm install -g vue/cli 使用 Vue CLI 创建一个新的 Vue 项目。 vue create admin-system cd admin-system npm run serve 出现这个页面表示vue创建成功 安…

3. 轴指令(omron 机器自动化控制器)——>MC_Stop

机器自动化控制器——第三章 轴指令 9 MC_Stop变量▶输入变量▶输出变量▶输入输出变量 功能说明▶指令详情▶时序图▶重启运动指令▶多重启动运动指令▶异常 MC_Stop 使轴减速停止。 指令名称FB/FUN图形表现ST表现MC_Stop强制停止FBMC_Stop_instance (Axis :《参数》 ,Execu…