std::async 和 std::packaged_task

0、背景

在现代 C++ 中,std::async 和 std::packaged_task 是两个非常重要的工具,能够帮助我们更好地处理并发和异步操作。它们分别代表了异步执行任务的两种不同的方式,但都可以有效地将任务的执行从主线程或调用线程中分离出来,以提高程序的并发性和响应能力。

1、std::async

1.1、std::async的基本语法

std::async 是 C++11 引入的一个函数模板,它用于启动一个异步操作,并返回一个 std::future 对象,这个 future 对象可以用来获取异步操作的结果。std::async 会根据指定的策略异步地执行一个函数或可调用对象。函数签名如下:

cpp 复制代码
std::future<T> std::async(std::launch policy, Func&& f, Args&&... args);
  • policy: 启动策略,决定任务如何执行。它有两个可能的值:
    std::launch::async :表示任务将异步执行(即在一个新的线程中执行);
    std::launch::deferred:表示任务将延迟执行,直到调用 future.get() 或 future.wait() 来获取结果时才开始执行。
  • f: 一个可调用对象(例如函数指针、lambda 表达式或函数对象)。
  • args: 传递给可调用对象的参数
    std::async 返回一个 std::future 类型的对象,T 是任务的返回类型。可以通过 future.get() 来获取任务的结果。需要注意的是,如果任务还没有完成,调用 get() 会阻塞直到任务完成。

1.2、使用用例

cpp 复制代码
#include <iostream>
#include <future>
#include <thread>

int async_task(int x, int y) {
    std::this_thread::sleep_for(std::chrono::seconds(2));  // 模拟耗时任务
    return x + y;
}

int main() {
    // 启动异步任务
    std::future<int> result = std::async(std::launch::async, async_task, 5, 3);

    // 在等待异步结果的同时,可以做其他工作
    std::cout << "Doing other work in main thread..." << std::endl;

    // 获取异步任务结果,get() 会阻塞直到任务完成
    int sum = result.get();
    std::cout << "Async task result: " << sum << std::endl;

    return 0;
}

在上面的例子中,std::async 启动了一个异步任务,并返回一个 std::future 对象,主线程可以在不阻塞的情况下继续做其他工作,直到调用 result.get() 时才会等待异步任务的完成并获取结果。

1.3、启动策略

std::launch 是一个枚举,决定任务的执行方式。

  • std::launch::async:表示任务应该在独立的线程中异步执行。也就是说,当调用 std::async(std::launch::async, func) 时,func 会在一个新的线程中执行
  • std::launch::deferred:表示任务会被延迟到调用 future.get() 或 future.wait() 时才执行。即任务不会在调用 std::async 时立即启动,而是在获取结果时才启动。

2、std::packaged_task

2.1、std::packaged_task的基本语法

std::packaged_task 是 C++11 中的另一项异步操作工具,它封装了一个可调用对象,并提供一个 std::future 来获取异步操作的结果。与 std::async 不同,std::packaged_task 并不自动启动任务,调用者需要手动执行该任务。基本语法如下:

cpp 复制代码
template <typename F>
class std::packaged_task;
  • F: 一个可调用对象的类型(例如函数指针、lambda 表达式等)。
  • std::packaged_task 封装了一个任务,并且提供了 get_future() 方法来获取对应的 std::future 对象。

2.2、使用例子

cpp 复制代码
#include <iostream>
#include <future>
#include <thread>

int task(int x, int y) {
    std::this_thread::sleep_for(std::chrono::seconds(2));  // 模拟耗时任务
    return x + y;
}

int main() {
    // 创建 packaged_task 对象
    std::packaged_task<int(int, int)> task_obj(task);
    
    // 获取与 packaged_task 关联的 future 对象
    std::future<int> result = task_obj.get_future();

    // 启动新线程来执行任务
    std::thread t(std::move(task_obj), 5, 3);

    // 在等待异步结果的同时,可以做其他工作
    std::cout << "Doing other work in main thread..." << std::endl;

    // 获取异步任务结果,get() 会阻塞直到任务完成
    int sum = result.get();
    std::cout << "Task result: " << sum << std::endl;

    // 等待线程结束
    t.join();

    return 0;
}

我们使用 std::packaged_task 封装了一个任务 task,然后调用 get_future() 获取一个 std::future 对象。任务并不会自动执行,因此我们需要显式地创建一个线程来执行这个任务。最后,我们通过 result.get() 获取结果,get() 会阻塞直到任务完成。

2.3、启动策略

std::async 自动处理线程的创建和管理,而 std::packaged_task 则要求手动创建线程并执行任务 。std::async 可以选择任务延迟执行(通过 std::launch::deferred),而 std::packaged_task 始终会在调用时被启动

3、std::async 和 std::packaged_task 对比

特性 std::async std::packaged_task
任务启动时机 自动启动,可以选择异步执行或延迟执行 任务需要手动启动(通过线程或其他方式)
线程管理 自动管理线程 需要显式创建线程来执行任务
返回结果 返回一个 std::future 对象,直接可以获取结果 返回一个 std::future 对象,需要通过线程来执行任务
使用场景 简单的异步任务,适用于无需控制线程生命周期的场景 需要显式控制任务执行时机和线程的场景
启动策略 std::launch::async 或 std::launch::deferred 不支持延迟执行,只能同步启动

4、使用场景

4.1、std::async 的使用场景

  • 当你只关心任务的异步执行,而不需要显式控制线程时,std::async 是一个非常方便的选择。
  • 它适用于简单的并发任务,且希望任务自动执行,不需要管理线程的生命周期。

4.2、std::packaged_task 的使用场景

  • 当你需要手动控制任务的执行时,例如当你希望任务在某个特定时刻或者通过某种特定的方式(如通过线程池)来启动时,std::packaged_task 会是一个合适的选择。
  • 它适用于需要与线程池、事件驱动等复杂并发机制一起使用的场景。
相关推荐
落叶慢慢13 分钟前
新校区布网
c++
我真不会起名字啊1 小时前
C/C++中的宏定义
c语言·c++·算法
FakeOccupational1 小时前
【电路笔记 TMS320F28335DSP】DSP C2833x C/C++ 头文件和外设示例下载+controlsuite 示例使用
c语言·c++·笔记
2301_775765522 小时前
QT——day1
c++
高一学习c++会秃头吗2 小时前
string
开发语言·c++
蓝天扶光3 小时前
C++算法第十一天
开发语言·c++·算法
木向5 小时前
leetcode17:电话号码的字母组合
开发语言·c++·算法
A_New_World5 小时前
BlockDeque
开发语言·c++
重生之我在字节当程序员5 小时前
解释工厂模式
开发语言·c++·简单工厂模式
SunshineBooming7 小时前
qemu源码解析【05】qemu启动初始化流程
c++·驱动开发·源码软件