C++ 异步编程的利器std::future和std::promise
1、背景
在现代计算机系统中,许多任务可能需要花费较长时间才能完成,例如网络请求、文件读取、大规模数据计算等。如果在程序中同步地执行这些任务,会导致主线程被阻塞,整个程序在任务执行期间无法响应其他操作,用户体验极差。异步编程应运而生,它允许程序在执行耗时任务的同时,不影响其他部分的正常运行,提高了程序的效率和灵活性。这里的其他部分通常运行在不同的cpu核上,当在其它核上的任务执行完毕,通知当前线程即可,当前线程可以在这段时间片执行其它的任务。
2、std::future的基本概念与用法
std::future 可以看作是一个 “占位符” 或者 “结果容器”,用于获取异步操作的结果。它提供了一种机制,使得当前线程能够在未来的某个时刻获取另一个线程中执行的异步任务的返回值。
2.1、获取异步任务结果
我们可以通过 std::async 函数来启动一个异步任务,并返回一个 std::future 对象。
#include <iostream>
#include <future>
#include <thread>int add(int a, int b) {std::this_thread::sleep_for(std::chrono::seconds(2)); // 模拟耗时操作return a + b;
}int main() {std::future<int> result = std::async(add, 3, 5);// 在此处可以执行其他操作,不会被 add 函数的执行阻塞int sum = result.get(); // 获取异步任务的结果,此时如果任务尚未完成,会阻塞当前线程直到任务完成std::cout << "The sum is: " << sum << std::endl;return 0;
}
在上述代码中,std::async 启动了 add 函数的异步执行,并返回一个 std::future 对象 result。在 result.get() 被调用时,如果 add 函数尚未完成,主线程会被阻塞等待结果。一旦 add 函数执行完毕,result.get() 就会获取到返回值并赋值给 sum。
2.2、等待异步任务完成
除了使用 get 方法获取结果时阻塞等待,std::future 还提供了 wait 方法来单纯地等待异步任务完成,而不获取结果。
#include <iostream>
#include <future>
#include <thread>void print_hello() {std::this_thread::sleep_for(std::chrono::seconds(1));std::cout << "Hello from the async task!" << std::endl;
}int main() {std::future<void> result = std::async(print_hello);std::cout << "Before waiting..." << std::endl;result.wait(); // 等待异步任务完成std::cout << "After waiting..." << std::endl;return 0;
}
在这个例子中,print_hello 函数在异步线程中执行,主线程通过 result.wait() 等待其完成。在等待期间,主线程可以执行其他非依赖于 print_hello 结果的操作,但在 wait 调用处会暂停,直到异步任务结束。
2.3、查询异步任务状态
std::future 还提供了 wait_for 方法来设置一个等待时间,用于查询异步任务是否在指定时间内完成。它返回一个 std::future_status 类型的枚举值,有以下几种情况:
- std::future_status::ready:表示异步任务已经完成,可以获取结果。
- std::future_status::timeout:表示在等待的时间内任务未完成。
- std::future_status::deferred:表示异步任务被延迟执行,只有在调用 get 时才会真正开始执行。
#include <iostream>
#include <future>
#include <thread>
#include <chrono>int multiply(int a, int b) {std::this_thread::sleep_for(std::chrono::seconds(3));return a * b;
}int main() {std::future<int> result = std::async(multiply, 4, 6);std::cout << "Checking task status..." << std::endl;std::future_status status = result.wait_for(std::chrono::seconds(2));if (status == std::future_status::ready) {std::cout << "Task completed. Result: " << result.get() << std::endl;} else if (status == std::future_status::timeout) {std::cout << "Task still in progress. Waiting more..." << std::endl;int product = result.get(); // 再次等待并获取结果std::cout << "Task completed. Result: " << product << std::endl;} else if (status == std::future_status::deferred) {std::cout << "Task is deferred." << std::endl;}return 0;
}
这个例子中,设置了 2 秒的等待时间。由于 multiply 函数实际需要 3 秒才能完成,所以在 2 秒时 wait_for 返回 std::future_status::timeout,然后我们再次调用 result.get() 来获取最终结果并等待任务完成。
3、std::promise 的基本概念与用法
std::promise 则用于设置异步操作的结果或异常,它与 std::future 紧密配合,实现了异步任务结果的传递。
3.1、设置异步任务结果
我们可以在一个线程中通过 std::promise 设置结果,而在另一个线程中通过对应的 std::future 获取该结果。
#include <iostream>
#include <future>
#include <thread>void set_value_in_thread(std::promise<int>& prom) {std::this_thread::sleep_for(std::chrono::seconds(1));prom.set_value(42); // 设置异步任务的结果值为 42
}int main() {std::promise<int> prom;// 建立关联std::future<int> fut = prom.get_future();std::thread t(set_value_in_thread, std::ref(prom));int value = fut.get(); // 获取设置的结果值std::cout << "The value set in the other thread is: " << value << std::endl;t.join();return 0;
}
在上述代码中,set_value_in_thread 函数在一个新线程中运行,通过传入的 std::promise 对象 prom 设置了结果值为 42。在主线程中,通过 fut.get() 获取到了这个结果值。
3.2、设置异步任务异常
std::promise 不仅可以设置结果值,还可以设置异常。
#include <iostream>
#include <future>
#include <thread>
#include <exception>void set_exception_in_thread(std::promise<int>& prom) {try {throw std::runtime_error("An error occurred in the async thread.");} catch (...) {prom.set_exception(std::current_exception()); // 设置异步任务的异常}
}int main() {std::promise<int> prom;std::future<int> fut = prom.get_future();std::thread t(set_exception_in_thread, std::ref(prom));try {fut.get(); // 尝试获取结果,会抛出之前设置的异常} catch (const std::runtime_error& e) {std::cout << "Caught exception: " << e.what() << std::endl;}t.join();return 0;
}
在这个例子中,set_exception_in_thread 函数在异步线程中抛出了一个异常,并通过 prom.set_exception(std::current_exception()) 将异常设置到 std::promise 对象中。在主线程中,当调用 fut.get() 时,就会捕获到这个异常并进行处理。
4、std::future 和 std::promise 的关联
std::future 和 std::promise 的关联是通过 std::promise 的 get_future 方法建立的。一旦建立关联,一个线程可以通过 std::promise 设置结果或异常,而另一个线程可以通过对应的 std::future 获取结果或处理异常。这种协同工作机制使得我们可以在不同的线程之间进行高效的结果传递和错误处理,非常适合构建复杂的异步任务处理流程。例如,在一个多线程的网络服务器中,一个线程负责接收网络请求并将处理任务分配给工作线程,工作线程完成任务后可以通过 std::promise 将结果返回给主线程,主线程再通过 std::future 获取结果并响应给客户端。
5、注意事项
- 一个 std::promise 对象只能设置一次结果或异常。一旦设置完成,再次调用 set_value 或 set_exception 将会抛出 std::future_error 异常。因此,在使用时要确保正确地设置结果或异常,避免重复设置。
- 在使用 std::future 和 std::promise 时,要注意线程之间的同步和资源管理。例如,在获取 std::future 的结果时,如果异步任务尚未完成,当前线程会被阻塞,这可能会影响程序的性能和响应性。因此,可以合理地结合 wait_for 等方法来进行超时处理或轮询检查任务状态,避免长时间的阻塞。同时,在创建线程和使用 std::promise 等对象时,要确保正确地管理线程资源,避免出现资源泄漏等问题。例如,在使用 std::thread 时,要记得在合适的时机调用 join 或 detach 方法。
- 在异步任务中抛出的异常,如果没有通过 std::promise 正确地设置到对应的 std::future 中,可能会导致异常被丢失或程序出现未定义行为。因此,要确保在异步任务中捕获所有可能的异常,并通过 set_exception 方法将异常传递给 std::future,以便在获取结果时能够正确地处理异常。
相关文章:
C++ 异步编程的利器std::future和std::promise
1、背景 在现代计算机系统中,许多任务可能需要花费较长时间才能完成,例如网络请求、文件读取、大规模数据计算等。如果在程序中同步地执行这些任务,会导致主线程被阻塞,整个程序在任务执行期间无法响应其他操作,用户体…...
CRM 系统中的 **知识库功能** 的设计与实现
CRM 系统中的 **知识库功能** 旨在为用户提供一个集中的平台,用于存储、组织和管理有关系统功能、常见问题、使用技巧、操作文档等信息。它能够帮助用户高效解决问题、快速获取所需信息,从而提升使用体验并减少客户支持负担。 ### 一、知识库功能的设计…...
重学设计模式-工厂模式(简单工厂模式,工厂方法模式,抽象工厂模式)
在平常的学习和工作中,我们创建对象一般会直接用new,但是很多时候直接new会存在一些问题,而且直接new会让我们的代码变得非常繁杂,这时候就会巧妙的用到设计模式,平常我们通过力扣学习的算法可能并不会在我们工作中用到…...
【C语言】结构体(四)
本篇重点是typedef关键字 一,是什么? typedef用来定义新的数据类型,通常typedef与结构体的定义配合使用。 简单来说就是取别名 ▶ struct 是用来定义新的数据类型——结构体 ▶ typedef是给数据类型取别名。 二,为什么…...
swift类方法为什么使用表派发?
直接上答案:因为表派发允许子类重写父类的方法,并在运行时根据对象的实际类型调用正确的方法实现。 什么是表派发? 首先我们先知道的是,swift当中函数的派发机制主要分为静态派发和动态派发。动态派发又分为表派发和消息派发。 …...
php实现AES/CBC/PKCS5Padding加密
接口文档 文档给过来的案例是java程序的,参照其思路,造一个php版本 构造aes对称加密 public static function encry($data){$data "要加密的数据";$key 你的256位密钥; // 密钥应该是16字节(128位),24字节…...
Anaconda3安装及使用
Anaconda3安装及使用 Linux中安装Anaconda31.安装 Anaconda32.配置环境变量3.验证是否成功 Conda环境和包管理1.Conda 环境初始化2.Conda Env 管理3.Conda 软件包管理 Linux中安装Anaconda3 下面是在Linux中安装Anaconda3-2021.05的教程,其他版本Anaconda更换名字即…...
Argon2-cffi与argon2-cffi-bindings:深入理解及其应用
Argon2-cffi与argon2-cffi-bindings的关系 在Python密码学领域,argon2-cffi和argon2-cffi-bindings是两个经常被提及的库。尽管它们的名字相似,但它们在实现和用途上有所不同。argon2-cffi是一个提供Argon2哈希算法的Python库,而argon2-cffi-…...
spring boot+jpa接入达梦数据库
文章目录 前言依赖配置对应的domain类和repository 前言 最近有一个新项目,由于信息安全等要求只能使用达梦数据库(dm8),之前从来没用过,特此开一个笔记记录一下spring bootjpa如何使用达梦数据库完成开发。 依赖 p…...
Vite构建,用NodeJS搭建一个简单的Vite服务
Vite 是一个现代的前端构建工具,由 Vue.js 作者尤雨溪创建。它主要用于开发和构建现代 JavaScript 应用,尤其是单页应用(SPA)。Vite 相比于传统的构建工具(如 Webpack)有几个显著的优势: 即时开…...
R语言机器学习论文(六):总结
文章目录 介绍参考文献介绍 本文采用R语言对来自进行数据描述、数据预处理、特征筛选和模型构建。 最后我们获得了一个能有效区分乳腺组织的随机森林预测模型,它的性能非常好,这意味着它可能拥有非常好的临床价值。 在本文中,我们利用R语言对来自美国加州大学欧文分校的B…...
python---面向对象---综合案例(4)
案例描述 实现加减乘法运算 # _*_ encoding:utf-8 _*_# 计算器, 实现一些基本的操作, 加减乘除运算, 以及打印结果操作# ------------------------------------代码1-------------------------------------- def jia(n1, n2):return n1 n2def jian(n1, n2):return n1 - n2de…...
如何参加华为欧拉考试?
华为欧拉考试主要针对的是华为欧拉(EulerOS/openEuler)操作系统的认证考试,这一认证体系旨在培养和认证具备基于欧拉操作系统进行企业级应用运行基础环境搭建、管理和调测能力的工程师以及云计算架构师。以下是对华为欧拉考试的详细介绍&…...
算法预刷题Day9:BM28 二叉树的最大深度
描述: 描述 求给定二叉树的最大深度, 深度是指树的根节点到任一叶子节点路径上节点的数量。 最大深度是所有叶子节点的深度的最大值。 (注:叶子节点是指没有子节点的节点。) 思路: 当前节点的最大高度 ma…...
exp_lr_scheduler理解
1. exp_lr_scheduler理解 这行代码定义了一个学习率调度器,用于动态调整训练过程中优化器的学习率。让我们分解并解释其含义: 1. exp_lr_scheduler 是什么? exp_lr_scheduler 是一个 学习率调度器(LR Scheduler),由 torch.optim.lr_scheduler.StepLR 创建,旨在按照预…...
Algorithm:河内之塔
1. 说明 河内之塔(Towers of Hanoi)是法国人 M.Claus(Lucas)于1883年从泰国带至法国的,河内为越战时北越的首都,即现在的胡志明市;1883年法国数学家 Edouard Lucas 曾提及这个故事,据…...
集中管理与实时审计:构建Linux集群(1300台服务器)日志平台的最佳实践
简介 随着企业IT基础设施的不断扩大,Linux服务器的数量也日益增多,传统的单机日志管理方式已无法满足对日志数据集中管理、审计和分析的需求。尤其是在大型集群环境中,如何高效地收集、存储和分析日志成为了一项重要的技术挑战。 背景 在实…...
在Scala中Array不可变的学习
package gjhs114import scala.collection.mutable.ArrayBuffer object Arrray114 {// 不可变数组:Array// def main(args: Array[String]): Unit {1 创建不可变数组// val arr1 Array(1,2,3)//2 访问.数组名(下标)。下标是从0开始到…...
vue3+vite 批量引入组件动态使用
import { ref, reactive, toRaw, markRaw, defineAsyncComponent, onMounted } from vue import type { Component } from vue// vue3vite 批量引入组件动态使用 const modules import.meta.glob<Component>(./details/*.vue) // 明确指定导入的模块类型为Component con…...
设计模式——方法链or流式接口
方法链或流式接口是一种编程模式或设计模式。核心思想是通过返回对象自身的应用,使得可以在一个表达式中连续调用多个方法。 c中实现这种模式 1.基本语法规则 (1)每个可链接的方法都返回对象自身的引用(通常是*this)…...
MMaDA: Multimodal Large Diffusion Language Models
CODE : https://github.com/Gen-Verse/MMaDA Abstract 我们介绍了一种新型的多模态扩散基础模型MMaDA,它被设计用于在文本推理、多模态理解和文本到图像生成等不同领域实现卓越的性能。该方法的特点是三个关键创新:(i) MMaDA采用统一的扩散架构…...
【Go】3、Go语言进阶与依赖管理
前言 本系列文章参考自稀土掘金上的 【字节内部课】公开课,做自我学习总结整理。 Go语言并发编程 Go语言原生支持并发编程,它的核心机制是 Goroutine 协程、Channel 通道,并基于CSP(Communicating Sequential Processes࿰…...
鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个医院查看报告小程序
一、开发环境准备 工具安装: 下载安装DevEco Studio 4.0(支持HarmonyOS 5)配置HarmonyOS SDK 5.0确保Node.js版本≥14 项目初始化: ohpm init harmony/hospital-report-app 二、核心功能模块实现 1. 报告列表…...
在Ubuntu中设置开机自动运行(sudo)指令的指南
在Ubuntu系统中,有时需要在系统启动时自动执行某些命令,特别是需要 sudo权限的指令。为了实现这一功能,可以使用多种方法,包括编写Systemd服务、配置 rc.local文件或使用 cron任务计划。本文将详细介绍这些方法,并提供…...
相机Camera日志分析之三十一:高通Camx HAL十种流程基础分析关键字汇总(后续持续更新中)
【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了:有对最普通的场景进行各个日志注释讲解,但相机场景太多,日志差异也巨大。后面将展示各种场景下的日志。 通过notepad++打开场景下的日志,通过下列分类关键字搜索,即可清晰的分析不同场景的相机运行流程差异…...
Map相关知识
数据结构 二叉树 二叉树,顾名思义,每个节点最多有两个“叉”,也就是两个子节点,分别是左子 节点和右子节点。不过,二叉树并不要求每个节点都有两个子节点,有的节点只 有左子节点,有的节点只有…...
管理学院权限管理系统开发总结
文章目录 🎓 管理学院权限管理系统开发总结 - 现代化Web应用实践之路📝 项目概述🏗️ 技术架构设计后端技术栈前端技术栈 💡 核心功能特性1. 用户管理模块2. 权限管理系统3. 统计报表功能4. 用户体验优化 🗄️ 数据库设…...
NXP S32K146 T-Box 携手 SD NAND(贴片式TF卡):驱动汽车智能革新的黄金组合
在汽车智能化的汹涌浪潮中,车辆不再仅仅是传统的交通工具,而是逐步演变为高度智能的移动终端。这一转变的核心支撑,来自于车内关键技术的深度融合与协同创新。车载远程信息处理盒(T-Box)方案:NXP S32K146 与…...
Python Ovito统计金刚石结构数量
大家好,我是小马老师。 本文介绍python ovito方法统计金刚石结构的方法。 Ovito Identify diamond structure命令可以识别和统计金刚石结构,但是无法直接输出结构的变化情况。 本文使用python调用ovito包的方法,可以持续统计各步的金刚石结构,具体代码如下: from ovito…...
系统掌握PyTorch:图解张量、Autograd、DataLoader、nn.Module与实战模型
本文较长,建议点赞收藏,以免遗失。更多AI大模型应用开发学习视频及资料,尽在聚客AI学院。 本文通过代码驱动的方式,系统讲解PyTorch核心概念和实战技巧,涵盖张量操作、自动微分、数据加载、模型构建和训练全流程&#…...
