C++ future/promise/thread/async/packaged_task入门

2024-01-12 07:04

本文主要是介绍C++ future/promise/thread/async/packaged_task入门,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

        std::promise、future、thread、async、packaged_task这些到底是个啥?

两种获取异步结果的方式

std::future

        std::future是一个同步原语,它代表了一个异步操作的结果。这个结果可能来自另一个线程、任务或者异步操作,而std::future提供了一种机制来访问这个结果。std::future通常与std::async、std::promise或std::packaged_task一起使用。
        std::future对象封装了一个值,这个值在将来某个时刻才会变得可用。当异步操作完成时,它的结果(或异常)会存储在std::future对象中。可以通过std::future对象来检索这个结果,或者等待异步操作完成。

获取std::future
可以通过以下几种方式获取std::future对象:

  • 通过std::async启动一个异步任务时,它会返回一个std::future对象。
  • 通过调用std::promise对象的get_future方法。
  • 通过std::packaged_task对象的get_future方法。

使用std::future获取结果

  • get()方法用来获取存储在其中的值。如果异步操作还没有完成,get方法会阻塞当前线程,直到值变得可用。调用完get,feature对象不再持有该结果。
  • wait()方法用来等待异步操作完成,而不获取结果

异常处理
        如果异步操作抛出了异常,这个异常会被捕获并存储在std::future对象中。当你调用get方法时,异常会被重新抛出。

try {int x = fut.get();
} catch (std::exception& e) {// 处理异常
}

std::future对象在默认构造时不关联任何异步操作。只有当它与一个特定的异步操作关联后,它才变得有用。std::future对象是不可复制的,但它们可以被移动。这意味着你可以将它们从一个函数传递到另一个函数,但不能创建它们的副本。

        通过std::future,开发者可以轻松地管理异步操作的结果,无论是来自std::async启动的任务,还是std::promise和std::packaged_task的结果。std::future提供了一种安全且简单的方式来同步线程间的操作,使得编写并发和异步代码变得更加容易和安全。

std::promise

        std::promise是一个同步原语,它提供了一种在一个线程中存储一个值或异常,并在另一个线程中通过std::future对象来检索它的机制。std::promise和std::future通常一起使用,以在线程间传递数据或通知。

创建std::promise

#include <chrono>
#include <thread>
#include <future>using namespace std;void SlowAdd(int a, int b, promise<int>&& p) {this_thread::sleep_for(std::chrono::seconds(1));p.set_value(a + b);
}int main() {promise<int> p;// 与每个std::promise对象关联的是一个std::future对象future<int> f = p.get_future();thread t(SlowAdd, 1, 2, move(p));cout << f.get() << endl;t.join();return 0;
}

输出:

code % ./a.out       
3  // after 1 sec

异常处理
如果在计算过程中发生异常,你可以使用std::promise的set_exception方法来存储异常:

try {// 执行一些可能抛出异常的计算
} catch (...) {myPromise.set_exception(std::current_exception());
}

        在另一个线程中,当你尝试通过std::future对象获取结果时,如果有异常被存储,它将被重新抛出。std::promise对象一旦通过set_value或set_exception设置了值或异常,就不能再次设置,否则将会抛出std::future_error异常。

        此外,std::promise对象在析构时会自动释放与之关联的资源,但如果你在析构前没有设置值或异常,与之关联的std::future对象在调用get()时将抛出std::future_error异常。

两种异步执行任务的方式

std::thread

        std::thread代表了一个单独的执行线程。创建一个std::thread对象时,你可以传递一个函数或者任何可调用对象(包括lambda表达式、函数指针、成员函数指针和具有operator()的对象)给它,这个函数或对象将在新线程中执行。

创建线程

#include <iostream>
#include <chrono>
#include <thread>using namespace std;void Add(int a, int b) {cout << "sum: " << a + b << endl;
}class C_Sub {
public:void Sub(int a, int b) {cout << "sub: " << a - b << endl;}
};int main() {thread t1(Add, 1, 2);t1.join();this_thread::sleep_for(std::chrono::seconds(1));C_Sub sub;thread t2(&C_Sub::Sub, sub, 3, 2);t2.join();return 0;
}

输出:

code % ./a.out       
sum: 3
sub: 1

线程管理
        创建线程后,就需要管理它的生命周期。通常,你需要在某个时刻等待线程结束,这可以通过调用join()来实现。如果你没有在std::thread对象销毁前调用join()或者detach(),或者重复调用了join()或detach(),那么在其析构函数中会调用std::terminate(),程序将异常终止。

  • join():等待线程结束。
  • detach():允许线程独立运行,即使创建它的std::thread对象已经被销毁。
  • joinable(): 判断一个thread是否是可join或detach的,即没有调用过join或detach

线程与异常
        如果线程函数抛出了一个未捕获的异常,std::terminate()将被调用,程序将终止。因此,你应该确保线程函数中的异常被妥善处理。

std::async

        std::async是一个函数模板,它用于异步执行一个任务,并返回一个std::future对象。这个std::future对象可以用来获取异步任务的结果。std::async可以选择立即启动一个新线程来执行任务,也可以延迟执行任务直到对应的std::future对象的get()或wait()被调用。

使用std::async启动异步任务

#include <iostream>
#include <chrono>
#include <future>using namespace std;int SlowAdd(int a, int b) {this_thread::sleep_for(std::chrono::seconds(1));return a + b;
}int main() {future<int> f = async(SlowAdd, 1, 2);cout << f.get() << endl;return 0;
}

输出:

code % ./a.out       
3  // after 1 sec

std::async的启动策略
        std::async可以接受一个可选的启动策略参数,这个参数控制异步任务的执行方式。

  • std::launch::async:指示std::async创建一个新线程来立即执行任务。
  • std::launch::deferred:指示std::async延迟执行任务,直到调用std::future对象的get()或wait()。

        如果不指定启动策略,std::async可能会根据实现选择最合适的策略。

异步任务的异常处理
        如果异步任务抛出了异常,这个异常不会立即传播到启动任务的线程。相反,异常会被存储在std::future对象中,当你调用get()来获取结果时,异常会被重新抛出。

std::async与std::thread的比较
        std::async与std::thread都可以用来执行并发任务,区别:

  • std::async返回一个std::future对象,可以用来获取任务的结果或异常,而std::thread不直接提供这样的机制。
  • std::async管理任务的生命周期,当std::future对象被销毁时,它会等待任务完成。而std::thread需要显式地调用join()或detach()。
  • std::async可以延迟执行任务,而std::thread总是立即启动一个新线程。

一种绑定feature与任务的方式

std::packaged_task

        std::packaged_task是C++11引入的一个模板类,它封装了一个可调用对象(如函数、lambda表达式、绑定表达式或其他函数对象),并允许其异步执行。它的一个关键特性是能够提供一个std::future对象,通过这个对象可以在未来某个时刻获取std::packaged_task所封装的可调用对象的返回值。

如何使用std::packaged_task

#include <iostream>
#include <chrono>
#include <future>using namespace std;int SlowAdd(int a, int b) {this_thread::sleep_for(std::chrono::seconds(1));return a + b;
}int main() {std::packaged_task<int(int)> task(SlowAdd);std::future<int> f = task.get_future();std::thread t(std::move(task), 1, 2);std::cout << f.get() << std::endl;t.join();return 0;
}

输出:

code % ./a.out       
3  // after 1 sec

异常处理
        如果std::packaged_task中的可调用对象在执行过程中抛出异常,这个异常将被捕获,并存储在与之关联的std::future对象中。当你调用std::future::get()时,如果有异常发生,它将被重新抛出。

重置任务
        std::packaged_task提供了一个reset成员函数,允许你重置任务的状态,这样你就可以重新使用std::packaged_task对象。但是,在重置之前,你必须确保已经获取了之前任务的结果,否则会抛出std::future_error异常。

有async为什么还要有packaged_task

        std::async是一个函数模板,它用于简化异步任务的创建和执行。当你调用std::async时,它会自动创建一个线程(或者将任务提交给线程池,这取决于实现和传递的策略参数),并立即开始执行指定的任务。std::async返回一个std::future对象,你可以通过它来获取任务的结果。

        std::async的优点在于它的简单性和便捷性。你不需要显式创建线程或管理线程的生命周期,一切都由std::async自动处理。

        std::packaged_task是一个类模板,它将一个可调用对象和一个std::future对象绑定在一起。与std::async不同,std::packaged_task不会自动启动任务执行,你需要手动调用它或将其传递给一个线程来执行。这意味着你可以更精细地控制任务的执行时机和方式。

这篇关于C++ future/promise/thread/async/packaged_task入门的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/597126

相关文章

linux报错INFO:task xxxxxx:634 blocked for more than 120 seconds.三种解决方式

《linux报错INFO:taskxxxxxx:634blockedformorethan120seconds.三种解决方式》文章描述了一个Linux最小系统运行时出现的“hung_ta... 目录1.问题描述2.解决办法2.1 缩小文件系统缓存大小2.2 修改系统IO调度策略2.3 取消120秒时间限制3

C++中实现调试日志输出

《C++中实现调试日志输出》在C++编程中,调试日志对于定位问题和优化代码至关重要,本文将介绍几种常用的调试日志输出方法,并教你如何在日志中添加时间戳,希望对大家有所帮助... 目录1. 使用 #ifdef _DEBUG 宏2. 加入时间戳:精确到毫秒3.Windows 和 MFC 中的调试日志方法MFC

深入理解C++ 空类大小

《深入理解C++空类大小》本文主要介绍了C++空类大小,规定空类大小为1字节,主要是为了保证对象的唯一性和可区分性,满足数组元素地址连续的要求,下面就来了解一下... 目录1. 保证对象的唯一性和可区分性2. 满足数组元素地址连续的要求3. 与C++的对象模型和内存管理机制相适配查看类对象内存在C++中,规

在 VSCode 中配置 C++ 开发环境的详细教程

《在VSCode中配置C++开发环境的详细教程》本文详细介绍了如何在VisualStudioCode(VSCode)中配置C++开发环境,包括安装必要的工具、配置编译器、设置调试环境等步骤,通... 目录如何在 VSCode 中配置 C++ 开发环境:详细教程1. 什么是 VSCode?2. 安装 VSCo

C# Task Cancellation使用总结

《C#TaskCancellation使用总结》本文主要介绍了在使用CancellationTokenSource取消任务时的行为,以及如何使用Task的ContinueWith方法来处理任务的延... 目录C# Task Cancellation总结1、调用cancellationTokenSource.

Python中的异步:async 和 await以及操作中的事件循环、回调和异常

《Python中的异步:async和await以及操作中的事件循环、回调和异常》在现代编程中,异步操作在处理I/O密集型任务时,可以显著提高程序的性能和响应速度,Python提供了asyn... 目录引言什么是异步操作?python 中的异步编程基础async 和 await 关键字asyncio 模块理论

C++11的函数包装器std::function使用示例

《C++11的函数包装器std::function使用示例》C++11引入的std::function是最常用的函数包装器,它可以存储任何可调用对象并提供统一的调用接口,以下是关于函数包装器的详细讲解... 目录一、std::function 的基本用法1. 基本语法二、如何使用 std::function

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

【C++ Primer Plus习题】13.4

大家好,这里是国中之林! ❥前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。有兴趣的可以点点进去看看← 问题: 解答: main.cpp #include <iostream>#include "port.h"int main() {Port p1;Port p2("Abc", "Bcc", 30);std::cout <<

C++包装器

包装器 在 C++ 中,“包装器”通常指的是一种设计模式或编程技巧,用于封装其他代码或对象,使其更易于使用、管理或扩展。包装器的概念在编程中非常普遍,可以用于函数、类、库等多个方面。下面是几个常见的 “包装器” 类型: 1. 函数包装器 函数包装器用于封装一个或多个函数,使其接口更统一或更便于调用。例如,std::function 是一个通用的函数包装器,它可以存储任意可调用对象(函数、函数