i007.cc

i007.cc

优先队列-降维打击

05.价值资料

std::async / std::future:更高级的异步任务

std::async / std::future:异步任务


🔷 一、为什么需要它?

std::thread 的问题:启动线程后拿不到返回值

cpp
// ❌ thread 无法返回值
int result;
std::thread t([&result]() {
    result = 42;  // 只能靠副作用"传出"结果,很麻烦
});
t.join();

 

std::async 解决了这个问题:启动异步任务,返回一个 future 对象,将来可以从中取结果。


🔷 二、基本用法

cpp
#include <future>
#include <iostream>

int heavyCalc(int x) {
    // 模拟耗时计算
    std::this_thread::sleep_for(std::chrono::seconds(2));
    return x * x;
}

int main() {
    // 启动异步任务,立即返回 future
    std::future<int> fut = std::async(std::launch::async, heavyCalc, 10);

    std::cout << "任务已启动,主线程继续干别的..." << std::endl;

    // 需要结果时再 get(),会阻塞直到任务完成
    int result = fut.get();
    std::cout << "结果:" << result << std::endl;  // → 100
}

 

future 就像一张”取货凭证”:任务在后台跑,你先忙别的,需要结果时凭证取货。


🔷 三、launch 策略

cpp
// 强制新线程运行
std::async(std::launch::async, task);

// 延迟执行:调用 get() 时才在当前线程运行(不开新线程)
std::async(std::launch::deferred, task);

// 默认:由系统决定(通常等同于 async)
std::async(task);

 

实际使用几乎总是用 std::launch::async,行为明确可控。


🔷 四、异常传播

future 能自动捕获异步任务中的异常,在 get() 时重新抛出:

cpp
auto fut = std::async(std::launch::async, []() -> int {
    throw std::runtime_error("任务出错了!");
    return 0;
});

try {
    int val = fut.get();  // 异常在这里重新抛出
} catch (const std::exception& e) {
    std::cout << "捕获异常:" << e.what();  // → "任务出错了!"
}

 


🔷 五、并行加速:多个 future 同时跑

cpp
// 串行:总共耗时 3s
int a = heavyCalc(3);   // 1s
int b = heavyCalc(4);   // 1s
int c = heavyCalc(5);   // 1s

// ✅ 并行:总共只耗时 ~1s
auto fa = std::async(std::launch::async, heavyCalc, 3);
auto fb = std::async(std::launch::async, heavyCalc, 4);
auto fc = std::async(std::launch::async, heavyCalc, 5);

// 三个任务同时在跑,最后统一取结果
int sum = fa.get() + fb.get() + fc.get();
// → 9 + 16 + 25 = 50

 


🔷 六、promise:手动控制 future

async 是自动的;promise 让你手动决定何时把值”放入” future:

cpp
#include <future>

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

// 在另一个线程中设置值
std::thread t([&prom]() {
    std::this_thread::sleep_for(std::chrono::seconds(1));
    prom.set_value(42);   // 把值"推入" future
});

std::cout << fut.get();  // 阻塞直到 set_value 被调用 → 42
t.join();

 

适用场景:回调式 API、事件驱动、手动控制完成时机。


🔷 七、shared_future:结果共享给多个消费者

普通 future 只能 get() 一次,shared_future 可以多次读取:

cpp
std::shared_future<int> sf = std::async(std::launch::async, []() {
    return 100;
}).share();  // 转为 shared_future

// 多个线程都可以读同一个结果
std::thread t1([sf]() { std::cout << sf.get(); });
std::thread t2([sf]() { std::cout << sf.get(); });

t1.join(); t2.join();
// 两个线程都输出 100,安全!

 


🔷 八、整体结构图

启动                  取结果
你  ──► async(task) ──► future ──► get() ──► 结果/异常
              │
              └──► 后台线程默默运行 task...


手动控制:
你  ──► promise ──► get_future() ──► future
              │
              └──► 任意时刻 set_value() 触发

 


🔷 选择指南

场景 用什么
简单异步任务,需要返回值 std::async
多任务并行,最后汇总 多个 std::future
回调/事件驱动,手动控制完成 std::promise
多个消费者共享同一结果 std::shared_future
不需要返回值,纯副作用 std::thread

下一步可以学:

  • std::packaged_task:把函数包装成可调度的任务
  • 线程池:复用线程,避免频繁创建销毁的开销
  • C++20 协程:更现代的异步方式(co_await

发表回复