The class template std::packaged_task wraps any callable target (function, lambda expression, bind expression, or another function object) so that it can be invoked asynchronously. Its return value or exception thrown is stored in a shared state which can be accessed through std::future objects.

std::packaged_task 是 C++11 标准库中的一个类模板,用于将可调用对象(如函数、lambda 表达式、函数对象等)包装起来,以便在异步任务中执行。同时,可调用对象的返回结果能够通过 std::packaged_task 返回的 future 得到。

# 1、参数

std::packaged_task 是一个模板类,其模板参数是可调用对象的签名,即返回类型和参数类型。例如,如果可调用对象是一个返回 int 类型并接受两个 int 参数的函数,则 std::packaged_task 的模板参数为 int(int, int)

# 2、使用示例

以下是一些使用 std::packaged_task 的示例:

# 2.1 基本用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
#include <future>
#include <thread>

int compute(int a, int b) {
return a + b;
}

int main() {
// 创建一个 packaged_task,包装 compute 函数
std::packaged_task<int(int, int)> task(compute);

// 获取与 task 相关联的 future 对象
std::future<int> result = task.get_future();

// 创建一个线程来执行 task
std::thread(std::move(task), 2, 3).detach();

// 等待并获取结果
std::cout << "Result: " << result.get() << std::endl;

return 0;
}

# 2.2 使用 lambda 表达式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
#include <future>
#include <thread>

int main() {
// 创建一个 packaged_task,包装 lambda 表达式
std::packaged_task<int(int, int)> task([](int a, int b) {
return a * b;
});

// 获取与 task 相关联的 future 对象
std::future<int> result = task.get_future();

// 创建一个线程来执行 task
std::thread(std::move(task), 3, 4).detach();

// 等待并获取结果
std::cout << "Result: " << result.get() << std::endl;

return 0;
}

# 3、优点

  • 灵活性:可以包装任意可调用对象,使其能够异步执行。
  • 结果传递:与 std::future 结合,提供一种从异步任务中获取结果的机制。
  • 与现有线程机制协作:可以与 std::thread 、线程池等并发机制一起使用,方便地管理异步任务。

总之, std::packaged_task 是一个非常有用的工具,适用于需要将可调用对象异步执行并获取结果的场景。通过结合 std::futurestd::thread ,它为实现并发编程提供了极大的灵活性和便利性。

对于 std::packaged_task ,它是否可以直接调用?它和用 std::thread 调用有什么区别?

std::packaged_task 的一个关键特点是它可以直接调用,也可以通过 std::thread 调用。直接调用 std::packaged_task 就像调用普通函数一样,而通过 std::thread 调用则是在线程中执行任务。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include <cmath>
#include <functional>
#include <future>
#include <iostream>
#include <thread>

// unique function to avoid disambiguating the std::pow overload set
int f(int x, int y) { return std::pow(x, y); }

void task_lambda()
{
std::packaged_task<int(int, int)> task([](int a, int b)
{
return std::pow(a, b);
});
std::future<int> result = task.get_future();

task(2, 9);

std::cout << "task_lambda:\t" << result.get() << '\n';
}

void task_bind()
{
std::packaged_task<int()> task(std::bind(f, 2, 11));
std::future<int> result = task.get_future();

task();

std::cout << "task_bind:\t" << result.get() << '\n';
}

void task_thread()
{
std::packaged_task<int(int, int)> task(f);
std::future<int> result = task.get_future();

std::thread task_td(std::move(task), 2, 10);
task_td.join();

std::cout << "task_thread:\t" << result.get() << '\n';
}

int main()
{
task_lambda();
task_bind();
task_thread();
}

直接调用与通过 std::thread 调用的区别

  • 直接调用:
    任务在当前线程中执行
    适用于需要立即执行任务并获取结果的场景
    简单,直接,不涉及多线程的复杂性
  • 通过 std::thread 调用:
    任务在新创建的线程中执行
    适用于需要并发执行任务的场景,可以提高性能
    涉及线程管理,需要处理线程的生命周期(如 join 或 detach)

既然它能够直接调用,那么它的作用是什么呢?
如果可以直接调用,又何苦要把原本就可以直接调用的可调用对象加一层封装呢?

std::packaged_task 的主要作用是将一个可调用对象与一个 std::future 关联起来,以便在不同线程之间传递任务并异步获取其结果。虽然 std::packaged_task 可以直接调用,但它的真正价值在于以下几个方面:

  1. 异步结果传递
    std::packaged_task 允许你将任务的执行与结果的获取分离。你可以在一个线程中设置任务,并在另一个线程中执行该任务,同时通过 std::future 异步获取结果。

  2. 任务封装与调度
    它可以将任务封装起来,使得任务可以被灵活地调度、传递和执行。这种封装使得任务的执行方式变得灵活,可以在当前线程执行,也可以通过线程池或其他线程执行。

  3. 异常处理
    通过 std::packaged_task 执行的任务,如果在执行过程中抛出异常,这些异常可以通过与其关联的 std::future 进行捕获和处理。

  4. 与其他并发工具集成
    std::packaged_task 可以与 std::threadstd::async 等其他并发工具一起使用,形成强大的并发编程模型。

# 4、小结

虽然 std::packaged_task 可以直接调用,但它的真正价值在于支持异步任务执行和结果获取。它提供了一种优雅的方式将任务的定义与执行分离,并与 std::future 结合使用,实现了强大的异步并发编程模型。在需要并发执行任务并获取结果的场景中, std::packaged_task 非常有用。

参考链接:
cppreference:packaged_task

更新于

请我喝杯咖啡吧~

Rick 微信支付

微信支付

Rick 支付宝

支付宝