packaged_task 、future知识点

以下是对 packaged_taskfuture 的详细解释,尽量简单易懂并结合例子说明:

1. packaged_task

  • 基本概念

    • packaged_task 是一个类模板,它将一个可调用对象(如函数、函数对象、lambda 表达式等)和一个 future 关联起来。它可以将可调用对象的结果存储在 future 中,以便在将来的某个时间点获取结果。
    • 可以将 packaged_task 看作是一个任务包装器,它包装了一个任务,并允许你在另一个线程中执行该任务,同时提供一种机制,让你可以在其他地方获取该任务的结果。
  • 使用示例

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

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

    int main() {
    // 将函数 add 包装在 packaged_task 中
    std::packaged_task<int(int, int)> task(add);
    // 获取与 packaged_task 关联的 future
    std::future<int> result = task.get_future();
    // 将 packaged_task 放到另一个线程中执行
    std::thread t(std::move(task), 3, 4);
    // 等待结果
    int sum = result.get();
    std::cout << "The sum is: " << sum << std::endl;
    t.join();
    return 0;
    }
    • 首先,使用 std::packaged_task<int(int, int)> task(add);add 函数包装在 packaged_task 中。
    • 然后,通过 task.get_future() 获取与之关联的 future 对象,这个 future 用于存储 add 函数的执行结果。
    • 启动一个新线程 std::thread t(std::move(task), 3, 4);,并将 task 移动到该线程中执行。这里使用 std::move 是因为 packaged_task 是不可复制的,只能移动。
    • 最后,通过 result.get() 等待并获取结果。

2. future

  • 基本概念

    • future 是一个类模板,提供了一种访问异步操作结果的机制。它可以表示一个可能尚未完成的操作,并允许你等待该操作完成并获取结果。
    • 当你调用 futureget() 方法时,会阻塞当前线程,直到关联的操作完成并返回结果。
  • 使用示例(继续上面的例子)

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

    int add(int a, int b) {
    std::this_thread::sleep_for(std::chrono::seconds(2)); // 模拟耗时操作
    return a + b;
    }

    int main() {
    std::packaged_task<int(int, int)> task(add);
    std::future<int> result = task.get_future();
    std::thread t(std::move(task), 3, 4);
    std::cout << "Waiting for the result..." << std::endl;
    // 阻塞等待结果
    int sum = result.get();
    std::cout << "The sum is: " << sum << std::endl;
    t.join();
    return 0;
    }
    • 在这个例子中,add 函数可能是一个耗时操作,我们通过 std::this_thread::sleep_for(std::chrono::seconds(2)); 来模拟。
    • result.get() 会阻塞 main 线程,直到 add 函数执行完成并将结果存储在 future 中,然后返回结果。

3. async 函数与 packaged_taskfuture 的关系

  • async函数是一个更高级的接口,它会自动将一个可调用对象包装在packaged_task 中,并返回一个future对象,使代码更简洁。

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

    int add(int a, int b) {
    std::this_thread::sleep_for(std::chrono::seconds(2));
    return a + b;
    }

    int main() {
    std::future<int> result = std::async(std::launch::async, add, 3, 4);
    std::cout << "Waiting for the result..." << std::endl;
    int sum = result.get();
    std::cout << "The sum is: " << sum << std::endl;
    return 0;
    }
    • std::async(std::launch::async, add, 3, 4); 会在另一个线程中异步执行 add 函数,并将结果存储在 future 中。
    • 你可以使用 std::launch::deferred 参数来延迟执行,直到调用 result.get() 时才开始执行任务。

4. 异常处理

  • 如果 packaged_task 中的可调用对象抛出异常,该异常会被存储在 future中。当你调用 future的get()方法时,异常会被重新抛出。

    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
    #include <iostream>
    #include <thread>
    #include <future>
    #include <functional>
    #include <exception>

    int divide(int a, int b) {
    if (b == 0) {
    throw std::runtime_error("Division by zero");
    }
    return a / b;
    }

    int main() {
    std::packaged_task<int(int, int)> task(divide);
    std::future<int> result = task.get_future();
    std::thread t(std::move(task), 5, 0);
    try {
    int quotient = result.get();
    std::cout << "The quotient is: " << quotient << std::endl;
    } catch (const std::exception& e) {
    std::cerr << "Exception: " << e.what() << std::endl;
    }
    t.join();
    return 0;
    }
    • divide 函数中 b 为 0 时,会抛出异常。
    • 调用 result.get() 时,异常会被捕获并处理。

总结

  • packaged_task
    • 是一个任务包装器,将可调用对象和 future 关联,可在另一个线程中执行任务并存储结果。
    • 可通过 get_future() 获取关联的 future 对象。
  • future
    • 用于获取异步操作的结果,调用 get() 会阻塞线程,直到结果可用。
    • 可处理异常,存储关联任务抛出的异常并在 get() 时重新抛出。

packaged_taskfuture 是 C++ 中进行异步编程和任务管理的重要工具,它们使你能够方便地在不同线程中执行任务并获取结果,同时处理异常,实现并发和并行计算