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

相关文章

C++如何通过Qt反射机制实现数据类序列化

《C++如何通过Qt反射机制实现数据类序列化》在C++工程中经常需要使用数据类,并对数据类进行存储、打印、调试等操作,所以本文就来聊聊C++如何通过Qt反射机制实现数据类序列化吧... 目录设计预期设计思路代码实现使用方法在 C++ 工程中经常需要使用数据类,并对数据类进行存储、打印、调试等操作。由于数据类

Linux下如何使用C++获取硬件信息

《Linux下如何使用C++获取硬件信息》这篇文章主要为大家详细介绍了如何使用C++实现获取CPU,主板,磁盘,BIOS信息等硬件信息,文中的示例代码讲解详细,感兴趣的小伙伴可以了解下... 目录方法获取CPU信息:读取"/proc/cpuinfo"文件获取磁盘信息:读取"/proc/diskstats"文

C++使用printf语句实现进制转换的示例代码

《C++使用printf语句实现进制转换的示例代码》在C语言中,printf函数可以直接实现部分进制转换功能,通过格式说明符(formatspecifier)快速输出不同进制的数值,下面给大家分享C+... 目录一、printf 原生支持的进制转换1. 十进制、八进制、十六进制转换2. 显示进制前缀3. 指

C#中async await异步关键字用法和异步的底层原理全解析

《C#中asyncawait异步关键字用法和异步的底层原理全解析》:本文主要介绍C#中asyncawait异步关键字用法和异步的底层原理全解析,本文给大家介绍的非常详细,对大家的学习或工作具有一... 目录C#异步编程一、异步编程基础二、异步方法的工作原理三、代码示例四、编译后的底层实现五、总结C#异步编程

C++中初始化二维数组的几种常见方法

《C++中初始化二维数组的几种常见方法》本文详细介绍了在C++中初始化二维数组的不同方式,包括静态初始化、循环、全部为零、部分初始化、std::array和std::vector,以及std::vec... 目录1. 静态初始化2. 使用循环初始化3. 全部初始化为零4. 部分初始化5. 使用 std::a

C++ vector的常见用法超详细讲解

《C++vector的常见用法超详细讲解》:本文主要介绍C++vector的常见用法,包括C++中vector容器的定义、初始化方法、访问元素、常用函数及其时间复杂度,通过代码介绍的非常详细,... 目录1、vector的定义2、vector常用初始化方法1、使编程用花括号直接赋值2、使用圆括号赋值3、ve

如何高效移除C++关联容器中的元素

《如何高效移除C++关联容器中的元素》关联容器和顺序容器有着很大不同,关联容器中的元素是按照关键字来保存和访问的,而顺序容器中的元素是按它们在容器中的位置来顺序保存和访问的,本文介绍了如何高效移除C+... 目录一、简介二、移除给定位置的元素三、移除与特定键值等价的元素四、移除满足特android定条件的元

Python获取C++中返回的char*字段的两种思路

《Python获取C++中返回的char*字段的两种思路》有时候需要获取C++函数中返回来的不定长的char*字符串,本文小编为大家找到了两种解决问题的思路,感兴趣的小伙伴可以跟随小编一起学习一下... 有时候需要获取C++函数中返回来的不定长的char*字符串,目前我找到两种解决问题的思路,具体实现如下:

C++ Sort函数使用场景分析

《C++Sort函数使用场景分析》sort函数是algorithm库下的一个函数,sort函数是不稳定的,即大小相同的元素在排序后相对顺序可能发生改变,如果某些场景需要保持相同元素间的相对顺序,可使... 目录C++ Sort函数详解一、sort函数调用的两种方式二、sort函数使用场景三、sort函数排序

Java调用C++动态库超详细步骤讲解(附源码)

《Java调用C++动态库超详细步骤讲解(附源码)》C语言因其高效和接近硬件的特性,时常会被用在性能要求较高或者需要直接操作硬件的场合,:本文主要介绍Java调用C++动态库的相关资料,文中通过代... 目录一、直接调用C++库第一步:动态库生成(vs2017+qt5.12.10)第二步:Java调用C++