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++ Primer 标准库vector示例详解

《C++Primer标准库vector示例详解》该文章主要介绍了C++标准库中的vector类型,包括其定义、初始化、成员函数以及常见操作,文章详细解释了如何使用vector来存储和操作对象集合,... 目录3.3标准库Vector定义和初始化vector对象通列表初始化vector对象创建指定数量的元素值

C++实现回文串判断的两种高效方法

《C++实现回文串判断的两种高效方法》文章介绍了两种判断回文串的方法:解法一通过创建新字符串来处理,解法二在原字符串上直接筛选判断,两种方法都使用了双指针法,文中通过代码示例讲解的非常详细,需要的朋友... 目录一、问题描述示例二、解法一:将字母数字连接到新的 string思路代码实现代码解释复杂度分析三、

C++一个数组赋值给另一个数组方式

《C++一个数组赋值给另一个数组方式》文章介绍了三种在C++中将一个数组赋值给另一个数组的方法:使用循环逐个元素赋值、使用标准库函数std::copy或std::memcpy以及使用标准库容器,每种方... 目录C++一个数组赋值给另一个数组循环遍历赋值使用标准库中的函数 std::copy 或 std::

C++使用栈实现括号匹配的代码详解

《C++使用栈实现括号匹配的代码详解》在编程中,括号匹配是一个常见问题,尤其是在处理数学表达式、编译器解析等任务时,栈是一种非常适合处理此类问题的数据结构,能够精确地管理括号的匹配问题,本文将通过C+... 目录引言问题描述代码讲解代码解析栈的状态表示测试总结引言在编程中,括号匹配是一个常见问题,尤其是在

使用C++实现链表元素的反转

《使用C++实现链表元素的反转》反转链表是链表操作中一个经典的问题,也是面试中常见的考题,本文将从思路到实现一步步地讲解如何实现链表的反转,帮助初学者理解这一操作,我们将使用C++代码演示具体实现,同... 目录问题定义思路分析代码实现带头节点的链表代码讲解其他实现方式时间和空间复杂度分析总结问题定义给定

C++初始化数组的几种常见方法(简单易懂)

《C++初始化数组的几种常见方法(简单易懂)》本文介绍了C++中数组的初始化方法,包括一维数组和二维数组的初始化,以及用new动态初始化数组,在C++11及以上版本中,还提供了使用std::array... 目录1、初始化一维数组1.1、使用列表初始化(推荐方式)1.2、初始化部分列表1.3、使用std::

C++ Primer 多维数组的使用

《C++Primer多维数组的使用》本文主要介绍了多维数组在C++语言中的定义、初始化、下标引用以及使用范围for语句处理多维数组的方法,具有一定的参考价值,感兴趣的可以了解一下... 目录多维数组多维数组的初始化多维数组的下标引用使用范围for语句处理多维数组指针和多维数组多维数组严格来说,C++语言没

c++中std::placeholders的使用方法

《c++中std::placeholders的使用方法》std::placeholders是C++标准库中的一个工具,用于在函数对象绑定时创建占位符,本文就来详细的介绍一下,具有一定的参考价值,感兴... 目录1. 基本概念2. 使用场景3. 示例示例 1:部分参数绑定示例 2:参数重排序4. 注意事项5.

使用C++将处理后的信号保存为PNG和TIFF格式

《使用C++将处理后的信号保存为PNG和TIFF格式》在信号处理领域,我们常常需要将处理结果以图像的形式保存下来,方便后续分析和展示,C++提供了多种库来处理图像数据,本文将介绍如何使用stb_ima... 目录1. PNG格式保存使用stb_imagephp_write库1.1 安装和包含库1.2 代码解

C++实现封装的顺序表的操作与实践

《C++实现封装的顺序表的操作与实践》在程序设计中,顺序表是一种常见的线性数据结构,通常用于存储具有固定顺序的元素,与链表不同,顺序表中的元素是连续存储的,因此访问速度较快,但插入和删除操作的效率可能... 目录一、顺序表的基本概念二、顺序表类的设计1. 顺序表类的成员变量2. 构造函数和析构函数三、顺序表