核心目标:掌握 C++ 异步编程四大组件,能用 std::asyncstd::future 进行异步任务编排,理解 shared state 的通信模型,避免常见陷阱。

前置知识:Part 1 的线程创建,Part 2 的 mutex 基础,lambda 表达式。


4.1 异步编程模型概述

4.1.1 同步 vs 异步

异步: 提交后继续

Main: Task A ──→ 提交 Task B ──→ Task C

Worker Thread: ... Task B ...

future.get() ← 结果

同步: 阻塞等待

Main: Task A ──→ Task B ──→ Task C

4.1.2 C++ 异步体系全景

Consumer (消费者)

Shared State
(共享状态)

Provider (生产者)

std::thread
自由控制

std::async
一行异步

std::promise
手动传值

std::packaged_task
包装 callable

值 / 异常
+ ready 标志

std::future
单一消费者

std::shared_future
多个消费者


4.2 std::future——获取异步结果

4.2.1 基本用法

#include <future>
#include <iostream>

std::future<int> the_answer;

void set_answer() {
    // 稍后通过 promise 设置值(4.3 详讲)
}

int main() {
    auto fut = the_answer;
    // fut.get() 会阻塞,直到值被设置
    // std::cout << fut.get() << "\n";
}

4.2.2 核心 API

std::future<int> fut = /* from async/promise/packaged_task */;

// get(): 阻塞等待 + 获取值(只能调用一次!)
int result = fut.get();
// fut.valid() == false 此后

// wait(): 仅等待,不获取值
fut.wait();

// wait_for(): 带超时等待
auto status = fut.wait_for(std::chrono::seconds(1));
if (status == std::future_status::ready) {
    // 就绪
} else if (status == std::future_status::timeout) {
    // 超时
} else if (status == std::future_status::deferred) {
    // 延迟执行 (async 的 deferred 策略)
}

// wait_until(): 等待到指定时间点
auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(5);
fut.wait_until(deadline);

// valid(): 检查是否与 shared state 关联
if (fut.valid()) { /* ... */ }

4.2.3 future 是 move-only

std::future<int> f1 = std::async([]{ return 42; });

// ❌ 不能拷贝
// std::future<int> f2 = f1;  // 编译错误!

// ✅ 可以移动
std::future<int> f2 = std::move(f1);
// f1.valid() == false

4.3 std::promise——手动传值的通道

4.3.1 基本使用

#include <future>
#include <thread>

void compute_in_thread(std::promise<int> p, int x, int y) {
    // 执行耗时计算
    std::this_thread::sleep_for(std::chrono::seconds(1));
    int result = x * x + y * y;

    p.set_value(result);  // 设置结果, 唤醒 future
}

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

    std::thread t(compute_in_thread, std::move(p), 3, 4);

    std::cout << "Waiting for result...\n";
    int result = fut.get();  // 阻塞, 直到 set_value
    std::cout << "Result: " << result << "\n";  // 25

    t.join();
}

4.3.2 传输异常

void risky_computation(std::promise<int> p) {
    try {
        throw std::runtime_error("computation failed");
        p.set_value(42);  // 不会执行
    } catch (...) {
        p.set_exception(std::current_exception());  // 传递异常
    }
}

int main() {
    std::promise<int> p;
    auto fut = p.get_future();
    std::thread t(risky_computation, std::move(p));

    try {
        int result = fut.get();  // 这里重新抛出异常!
    } catch (const std::exception& e) {
        std::cout << "Caught: " << e.what() << "\n";
    }

    t.join();
}

4.3.3 promise-future 通信模型

Main Thread future shared state promise Worker Thread Main Thread future shared state promise Worker Thread valid() = false get_future() 绑定 set_value(42) 写入 42 + ready=true 通知就绪 get() 读取 42 返回 42

4.4 std::async——一行启动异步任务

4.4.1 基本用法

#include <future>
#include <iostream>

int heavy_computation(int n) {
    int sum = 0;
    for (int i = 0; i < n * 1000000; ++i) {
        sum += i;
    }
    return sum;
}

int main() {
    // 一行代码: 启动异步计算
    std::future<int> fut = std::async(heavy_computation, 100);

    // 主线程可以做其他事情
    std::cout << "Computing in background...\n";

    // 获取结果 (阻塞等待)
    int result = fut.get();
    std::cout << "Result: " << result << "\n";
}

4.4.2 三种启动策略

// ═══ 策略 1: std::launch::async — 强制异步 (新线程) ═══
auto f1 = std::async(std::launch::async, []{ return 42; });
// ✅ 保证在新线程中执行

// ═══ 策略 2: std::launch::deferred — 延迟执行 ═══
auto f2 = std::async(std::launch::deferred, []{ return 42; });
// 任务推迟到 f2.get() / f2.wait() 时才在当前线程执行
// → 没有新线程创建, 相当于 lazy evaluation

// ═══ 策略 3: 默认 (async | deferred) — 危险! ═══
auto f3 = std::async([]{ return 42; });
// 运行时自行决定 async 还是 deferred
// ⚠️ 行为不确定!
策略 新线程? 何时执行 何时用
std::launch::async ✅ 是 立即 (新线程) 明确要并行
std::launch::deferred ❌ 否 get()/wait() lazy 计算
async | deferred (默认) ❓ 不确定 ❓ 不确定 不建议

最佳实践:始终显式指定策略,不要依赖默认。

4.4.3 async 返回的 future 析构会阻塞

这是 std::async 最臭名昭著的陷阱:

void dangerous() {
    // ❌ 临时 future 对象立即析构 → 阻塞!
    std::async(std::launch::async, [] {
        std::this_thread::sleep_for(std::chrono::seconds(5));
        return 42;
    });
    // ↑ future 析构函数会阻塞等待任务完成!
    // dangerous() 会卡住 5 秒
}

void safe() {
    // ✅ 把 future 存到变量里, 延长生命周期
    auto fut = std::async(std::launch::async, [] {
        std::this_thread::sleep_for(std::chrono::seconds(5));
        return 42;
    });
    // 现在可以继续执行, 稍后 fut.get()
}

4.4.4 async vs 手动 thread

特性 std::async 手动 std::thread
代码量 1 行 5+ 行
返回值 future 自动 ❌ 需手动传参/共享变量
异常传播 future.get() 自动 ❌ 需手动 catch + 传递
线程管理 自动 join/detach 必须手动处理
线程数控制 ❌ 不可控 ✅ 可控 (配合线程池)

4.5 std::packaged_task——包装可调用对象

4.5.1 基本用法

#include <future>
#include <thread>

int factorial(int n) {
    int result = 1;
    for (int i = 2; i <= n; ++i) result *= i;
    return result;
}

int main() {
    // packaged_task 包装一个可调用对象
    std::packaged_task<int(int)> task(factorial);
    // task 本身是可调用的 (operator())

    // 获取关联的 future
    std::future<int> fut = task.get_future();

    // 将 task 传递给工作线程
    std::thread t(std::move(task), 10);  // 执行 factorial(10)
    t.join();

    std::cout << "10! = " << fut.get() << "\n";  // 3628800
}

4.5.2 典型场景:GUI 线程分发任务

// GUI 线程需要把耗时任务丢给工作线程
class TaskDispatcher {
    std::deque<std::packaged_task<void()>> tasks_;
    std::mutex mtx_;
    std::condition_variable cv_;
    std::thread worker_;

public:
    TaskDispatcher() {
        worker_ = std::thread([this] {
            while (true) {
                std::packaged_task<void()> task;
                {
                    std::unique_lock lock(mtx_);
                    cv_.wait(lock, [this]{ return !tasks_.empty(); });
                    task = std::move(tasks_.front());
                    tasks_.pop_front();
                }
                task();  // 执行
            }
        });
    }

    template <typename F>
    std::future<void> submit(F&& f) {
        std::packaged_task<void()> task(std::forward<F>(f));
        auto fut = task.get_future();
        {
            std::lock_guard lock(mtx_);
            tasks_.push_back(std::move(task));
        }
        cv_.notify_one();
        return fut;
    }
};

4.6 std::shared_future——多消费者

4.6.1 为什么需要 shared_future

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

// ❌ future 只能 get() 一次
// int v1 = fut.get();  // 获取
// int v2 = fut.get();  // ❌ valid() == false

// ✅ shared_future 可以被多次获取
std::shared_future<int> shared = fut.share();
// 或直接: std::shared_future<int> shared = p.get_future();

int v1 = shared.get();  // ✅
int v2 = shared.get();  // ✅ 仍然有效

4.6.2 多个线程等待同一个结果

std::promise<int> p;
std::shared_future<int> shared = p.get_future();

auto worker = [shared](int id) {
    int result = shared.get();  // 全部阻塞, 等待同一个值
    std::cout << "Thread " << id << " got: " << result << "\n";
};

std::thread t1(worker, 1);
std::thread t2(worker, 2);
std::thread t3(worker, 3);

std::this_thread::sleep_for(std::chrono::seconds(1));
p.set_value(42);  // 所有 wait 同时被唤醒!

t1.join(); t2.join(); t3.join();
// 输出 (顺序不定):
// Thread 1 got: 42
// Thread 3 got: 42
// Thread 2 got: 42

4.7 异常处理

void throwing_task(std::promise<int> p) {
    p.set_exception(
        std::make_exception_ptr(std::runtime_error("task failed")));
}

int main() {
    // async 自动传播异常
    auto fut = std::async(std::launch::async, []() -> int {
        throw std::runtime_error("async failed");
        return 0;
    });

    try {
        int x = fut.get();  // 异常在此重新抛出
    } catch (const std::exception& e) {
        std::cout << "Caught from async: " << e.what() << "\n";
    }

    // packaged_task 同样自动传播
    std::packaged_task<int()> task([]() -> int {
        throw std::runtime_error("task failed");
        return 0;
    });
    auto fut2 = task.get_future();
    task();

    try {
        fut2.get();
    } catch (const std::exception& e) {
        std::cout << "Caught from task: " << e.what() << "\n";
    }
}

4.8 常见陷阱与最佳实践

陷阱 1:future 析构阻塞

// ❌ 临时 future 立即析构 → 该行卡住 5 秒
std::async(std::launch::async, heavy_work);

// ✅ 存为变量
auto fut = std::async(std::launch::async, heavy_work);
// 稍后 fut.get();

陷阱 2:默认启动策略

// ❌ 不确定: 开新线程还是 lazy?
auto f1 = std::async(heavy_work);

// ✅ 显式指定
auto f2 = std::async(std::launch::async, heavy_work);

陷阱 3:线程数爆炸

// ❌ 循环创建 async → 可能创建成千上万个线程
for (int i = 0; i < 100000; ++i) {
    auto fut = std::async(std::launch::async, [i] { return i * i; });
}

// ✅ 限制并发度 (用线程池)
ThreadPool pool(8);  // Part 7 详解
for (int i = 0; i < 100000; ++i) {
    pool.submit([i] { return i * i; });
}

陷阱 4:promise 生命周期

// ❌ promise 析构了但 future 还活着 → future.get() 抛 broken_promise
std::future<int> bad() {
    std::promise<int> p;
    auto fut = p.get_future();
    return fut;  // p 被销毁, fut 关联的 shared state 被 abandon
}

// ✅ 确保 promise 在 future 之前不销毁
void good() {
    std::promise<int> p;
    auto fut = p.get_future();
    std::thread([p = std::move(p)]() mutable {
        p.set_value(42);  // promise 移动到线程中, 生命周期被延长
    }).detach();
}

4.9 组件速查表

组件 角色 创建方式 获取结果
std::async Provider std::async(policy, func, args) 返回 future
std::promise<T> Provider p.get_future() 绑定 p.set_value(v)
std::packaged_task<R(Args...)> Provider task(args...) 执行 task.get_future()
std::future<T> Consumer 从 Provider 获得 fut.get() (一次)
std::shared_future<T> Consumer fut.share() shared.get() (多次)

4.10 小结

知识点 掌握程度 核心要点
future 基本 API 掌握 get/wait/wait_for/valid, move-only
promise-future 通信 掌握 set_value → shared state → get
async 启动策略 掌握 显式指定 async,不用默认
future 析构阻塞 掌握 async 的 future 析构会阻塞,必须存变量
packaged_task 理解 包装 callable,适合任务队列
shared_future 掌握 多消费者共享,fut.share() 获得
异常传播 掌握 set_exception → get 时重新抛出
线程数爆炸 了解 async 循环 → 配合线程池

下期预告

[Part 5:C++17 并行算法] 将展示如何用一行改动将 STL 算法并行化:

  • std::execution::par / par_unseq 执行策略
  • std::sort / std::for_each / std::reduce 的并行版本
  • 性能对比:串行 vs 并行 vs 并行+向量化
  • 并行算法的异常处理与注意事项

推荐工具

  • Compiler Explorer (godbolt.org) —— 查看 async 生成的线程创建代码
  • top -H —— 监控 async 创建的线程数
  • -fsanitize=thread —— 检测 promise/future 的错误使用

更多推荐