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

相关文章

Windows下C++使用SQLitede的操作过程

《Windows下C++使用SQLitede的操作过程》本文介绍了Windows下C++使用SQLite的安装配置、CppSQLite库封装优势、核心功能(如数据库连接、事务管理)、跨平台支持及性能优... 目录Windows下C++使用SQLite1、安装2、代码示例CppSQLite:C++轻松操作SQ

C++中RAII资源获取即初始化

《C++中RAII资源获取即初始化》RAII通过构造/析构自动管理资源生命周期,确保安全释放,本文就来介绍一下C++中的RAII技术及其应用,具有一定的参考价值,感兴趣的可以了解一下... 目录一、核心原理与机制二、标准库中的RAII实现三、自定义RAII类设计原则四、常见应用场景1. 内存管理2. 文件操

C++中零拷贝的多种实现方式

《C++中零拷贝的多种实现方式》本文主要介绍了C++中零拷贝的实现示例,旨在在减少数据在内存中的不必要复制,从而提高程序性能、降低内存使用并减少CPU消耗,零拷贝技术通过多种方式实现,下面就来了解一下... 目录一、C++中零拷贝技术的核心概念二、std::string_view 简介三、std::stri

C++高效内存池实现减少动态分配开销的解决方案

《C++高效内存池实现减少动态分配开销的解决方案》C++动态内存分配存在系统调用开销、碎片化和锁竞争等性能问题,内存池通过预分配、分块管理和缓存复用解决这些问题,下面就来了解一下... 目录一、C++内存分配的性能挑战二、内存池技术的核心原理三、主流内存池实现:TCMalloc与Jemalloc1. TCM

C++ 函数 strftime 和时间格式示例详解

《C++函数strftime和时间格式示例详解》strftime是C/C++标准库中用于格式化日期和时间的函数,定义在ctime头文件中,它将tm结构体中的时间信息转换为指定格式的字符串,是处理... 目录C++ 函数 strftipythonme 详解一、函数原型二、功能描述三、格式字符串说明四、返回值五

C++作用域和标识符查找规则详解

《C++作用域和标识符查找规则详解》在C++中,作用域(Scope)和标识符查找(IdentifierLookup)是理解代码行为的重要概念,本文将详细介绍这些规则,并通过实例来说明它们的工作原理,需... 目录作用域标识符查找规则1. 普通查找(Ordinary Lookup)2. 限定查找(Qualif

深入解析 Java Future 类及代码示例

《深入解析JavaFuture类及代码示例》JavaFuture是java.util.concurrent包中用于表示异步计算结果的核心接口,下面给大家介绍JavaFuture类及实例代码,感兴... 目录一、Future 类概述二、核心工作机制代码示例执行流程2. 状态机模型3. 核心方法解析行为总结:三

C/C++ chrono简单使用场景示例详解

《C/C++chrono简单使用场景示例详解》:本文主要介绍C/C++chrono简单使用场景示例详解,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友... 目录chrono使用场景举例1 输出格式化字符串chrono使用场景China编程举例1 输出格式化字符串示

C++/类与对象/默认成员函数@构造函数的用法

《C++/类与对象/默认成员函数@构造函数的用法》:本文主要介绍C++/类与对象/默认成员函数@构造函数的用法,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录名词概念默认成员函数构造函数概念函数特征显示构造函数隐式构造函数总结名词概念默认构造函数:不用传参就可以

C++类和对象之默认成员函数的使用解读

《C++类和对象之默认成员函数的使用解读》:本文主要介绍C++类和对象之默认成员函数的使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、默认成员函数有哪些二、各默认成员函数详解默认构造函数析构函数拷贝构造函数拷贝赋值运算符三、默认成员函数的注意事项总结一