掌控异步任务:解析 C++ 中 future、promise、packaged_task 与 async

掌控异步任务:解析 C++ 中 future、promise、packaged_task 与 async

编码文章call10242024-12-20 13:46:4751A+A-

1. std::future

std::future 是获取异步任务结果的核心工具。它提供了一种机制,使我们能够等待异步操作完成并获取其结果。

1.1 工作原理

std::future 和 std::promise 配合工作:

  • std::promise 设置任务的结果。
  • std::future 是获取这些结果的接口。 当结果尚未准备好时,调用 get() 会阻塞当前线程,直到结果可用。

1.2 API 详解

主要函数

get()

  • 获取异步结果。如果结果尚未准备好,会阻塞直到结果可用。
  • 一旦调用 get(),结果被取走,不能再次调用。
  • 如果任务抛出异常,get() 会重新抛出该异常。

wait()

  • 等待结果就绪,但不获取结果。

wait_for() 和 wait_until()

  • 使用超时机制等待结果。
  • 返回值:
  • std::future_status::ready 表示结果已就绪。
  • std::future_status::timeout 表示超时。
  • std::future_status::deferred 表示任务被延迟(通常与 std::launch::deferred 结合使用)。

注意事项

  • std::future 不可复制,只能移动。
  • 当任务发生异常,std::future 会通过 get() 抛出对应异常。

示例

#include <iostream>
#include <future>
#include <thread>

void producer(std::promise<int> &prom) {
    try {
        std::this_thread::sleep_for(std::chrono::seconds(2));
        prom.set_value(10);
    } catch (...) {
        prom.set_exception(std::current_exception());
    }
}

int main() {
    std::promise<int> prom;
    std::future<int> fut = prom.get_future();

    std::thread t(producer, std::ref(prom));
    std::cout << "Waiting for result..." << std::endl;
    std::cout << "Result: " << fut.get() << std::endl; // 阻塞直到结果就绪
    t.join();
    return 0;
}

2. std::promise

std::promise 是一个生产者工具,用于将任务的结果设置给 std::future。

2.1 原理

std::promise 的作用是创建一个共享状态,并允许生产者通过 set_value 或 set_exception 向共享状态中写入数据。std::future 是消费这一数据的接口。

2.2 使用方法

主要函数

set_value(T value)

  • 将结果写入到共享状态。

set_exception(std::exception_ptr e)

  • 设置异常,当消费者调用 get() 时,抛出该异常。

get_future()

  • 获取与 std::promise 关联的 std::future。

注意事项

  • std::promise 只能设置一次结果,否则会抛出异常 std::future_error。
  • 如果 std::promise 对象销毁时未设置结果,std::future 的 get() 会抛出异常。

示例

#include <iostream>
#include <future>
#include <thread>

void task(std::promise<std::string> prom) {
    try {
        std::this_thread::sleep_for(std::chrono::seconds(2));
        prom.set_value("Hello from promise!");
    } catch (...) {
        prom.set_exception(std::current_exception());
    }
}

int main() {
    std::promise<std::string> prom;
    std::future<std::string> fut = prom.get_future();

    std::thread t(task, std::move(prom));

    std::cout << "Waiting for result..." << std::endl;
    try {
        std::cout << fut.get() << std::endl;
    } catch (const std::exception &e) {
        std::cerr << "Error: " << e.what() << std::endl;
    }

    t.join();
    return 0;
}

3. std::packaged_task

std::packaged_task 是将一个可调用对象(函数、lambda 等)与异步结果(std::future)绑定的工具。

3.1 原理

  1. 将一个可调用对象封装到 std::packaged_task 中。
  2. 调用封装的任务时,它会自动将结果写入共享状态,消费者通过关联的 std::future 获取结果。

3.2 使用方法

主要函数

构造函数

  • 使用可调用对象(函数、函数对象或 lambda)初始化任务。

operator()

  • 执行任务,计算结果。

get_future()

  • 获取关联的 std::future。

注意事项

  • 和 std::promise 类似,std::packaged_task 只能设置一次结果。
  • 任务执行后,结果存储在 std::future 中。

示例

#include <iostream>
#include <future>
#include <thread>

int compute(int x, int y) {
    return x + y;
}

int main() {
    std::packaged_task<int(int, int)> task(compute); // 包装任务
    std::future<int> fut = task.get_future();        // 获取 future

    std::thread t(std::move(task), 10, 20); // 在线程中执行任务
    t.join();

    std::cout << "Result: " << fut.get() << std::endl; // 获取结果
    return 0;
}

4. std::async

std::async 是高层次的工具,用于快速启动异步任务。

4.1 原理

std::async 启动任务并自动管理其生命周期。它返回一个 std::future,用于获取任务的结果。

启动策略

std::launch::async

  • 在新线程中异步启动任务。

std::launch::deferred

  • 任务被延迟执行,只有在调用 get() 或 wait() 时才会运行。

默认策略

  • 具体行为由实现决定,可能选择异步启动或延迟执行。

4.2 使用方法

示例

#include <iostream>
#include <future>
#include <thread>

int compute(int x) {
    std::this_thread::sleep_for(std::chrono::seconds(2));
    return x * x;
}

int main() {
    std::future<int> fut = std::async(std::launch::async, compute, 10);

    std::cout << "Doing other work..." << std::endl;
    std::cout << "Result: " << fut.get() << std::endl; // 获取结果
    return 0;
}

5. 四者的关系与对比

5.1 功能比较

工具

功能

特点

std::future

提供异步结果的读取接口

只负责读取,需搭配其他工具使用。

std::promise

手动设置异步结果或异常

提供生产者角色,控制更灵活。

std::packaged_task

封装任务并生成 std::future

自动调用任务并存储结果。

std::async

启动异步任务并返回 std::future

高层次工具,简化线程管理。

5.2 适用场景

std::future

  • 需要获取任务结果,但无需创建或控制任务。
  • 适用于轻量级结果获取。

std::promise

  • 需要手动设置任务结果或传递异常。
  • 适用于生产者-消费者模型。

std::packaged_task

  • 需要封装已有任务并异步执行。
  • 适用于将现有逻辑整合为异步任务。

std::async

  • 简单启动并行任务,不需要额外线程管理。
  • 适用于轻量级并行计算。

6. 常见问题与优化

6.1 超时机制

wait_for 和 wait_until 提供超时等待功能,避免长期阻塞:

if (fut.wait_for(std::chrono::seconds(1)) == std::future_status::timeout) {
    std::cout << "Task timeout!" << std::endl; 
}

6.2 异常处理

异步任务可能抛出异常,`std::future` 会传播异常到消费者:

try {
    fut.get();
} catch (const std::exception &e) {
    std::cerr << "Error: " << e.what() << std::endl;
}

6.3 线程安全性

  • std::future 和 std::promise 本身是线程安全的。
  • 需要确保其他共享数据的访问同步。

7.C++ 异步编程工具总结

C++ 提供了一套强大的异步编程工具,帮助开发者简化多线程任务的结果管理和线程间通信。这些工具包括:

  1. std::future
    用于异步获取任务结果的占位符,是消费异步结果的核心工具。通过 get() 获取结果,阻塞线程直到结果可用。
  2. std::promise
    一个生产者工具,用于向共享状态手动设置任务结果或异常。它与 std::future 配合使用,实现任务结果的写入和读取。
  3. std::packaged_task
    封装一个可调用对象(如函数或 lambda),将其结果与 std::future 绑定,允许任务独立运行并异步返回结果。
  4. std::async
    高层次的接口,用于快速启动异步任务。它自动管理任务生命周期,并返回与任务结果关联的 std::future。

设计意义

  • 提升并发性能:在多核系统上充分利用资源,实现任务并行化。
  • 简化线程管理:通过高层接口(如 std::async),减少手动管理线程的复杂性。
  • 安全通信机制:通过 std::future 和 std::promise 提供线程安全的任务结果传递。

适用场景

  • 轻量级任务并行化:std::async 和 std::future。
  • 手动控制任务结果:std::promise 和 std::packaged_task。
  • 封装复杂计算逻辑:std::packaged_task。

C++ 异步编程工具灵活且强大,适用于从简单任务到复杂并发场景,帮助开发者更高效地编写可靠的多线程程序。

点击这里复制本文地址 以上内容由文彬编程网整理呈现,请务必在转载分享时注明本文地址!如对内容有疑问,请联系我们,谢谢!
qrcode

文彬编程网 © All Rights Reserved.  蜀ICP备2024111239号-4