C++ 异步编程(future、promise、packaged_task、async)

C++ 异步编程完全指南

本文档详细介绍C++11引入的异步编程工具:std::futurestd::promisestd::packaged_taskstd::async,帮助你在多线程环境下高效地进行任务管理和结果获取。

目录

  • [1. 异步编程基础](#1. 异步编程基础)
    • [1.1 什么是异步编程](#1.1 什么是异步编程)
    • [1.2 为什么需要异步编程](#1.2 为什么需要异步编程)
    • [1.3 核心组件概览](#1.3 核心组件概览)
  • [2. std::future - 异步结果的接收者](#2. std::future - 异步结果的接收者)
    • [2.1 基本概念](#2.1 基本概念)
    • [2.2 主要方法](#2.2 主要方法)
    • [2.3 代码示例](#2.3 代码示例)
  • [3. std::promise - 异步结果的提供者](#3. std::promise - 异步结果的提供者)
    • [3.1 基本概念](#3.1 基本概念)
    • [3.2 使用方法](#3.2 使用方法)
    • [3.3 代码示例](#3.3 代码示例)
  • [4. std::packaged_task - 可调用对象的包装器](#4. std::packaged_task - 可调用对象的包装器)
    • [4.1 基本概念](#4.1 基本概念)
    • [4.2 使用方法](#4.2 使用方法)
    • [4.3 代码示例](#4.3 代码示例)
  • [5. std::async - 最简单的异步任务启动](#5. std::async - 最简单的异步任务启动)
    • [5.1 基本概念](#5.1 基本概念)
    • [5.2 启动策略](#5.2 启动策略)
    • [5.3 代码示例](#5.3 代码示例)
  • [6. 四者对比与选择](#6. 四者对比与选择)
  • [7. 实际应用场景](#7. 实际应用场景)
  • [8. 最佳实践](#8. 最佳实践)
  • [9. 常见问题](#9. 常见问题)

1. 异步编程基础

1.1 什么是异步编程

异步编程是一种编程范式,允许程序在等待某个耗时操作完成时,继续执行其他任务,而不是阻塞等待。

同步 vs 异步:

cpp 复制代码
// 同步方式(阻塞)
int result = long_running_computation();  // 等待完成
do_something_else();  // 必须等上面完成后才能执行

// 异步方式(非阻塞)
std::future<int> result = std::async(long_running_computation);  // 立即返回
do_something_else();  // 可以立即执行
int value = result.get();  // 需要时再获取结果

1.2 为什么需要异步编程

优势:

  • 提高响应性:主线程不会被长时间阻塞
  • 充分利用多核:多个任务可以并发执行
  • 简化代码:比手动管理线程更简单
  • 异常安全:异常可以通过 future 传递

适用场景:

  • 文件I/O操作
  • 网络请求
  • 复杂计算
  • 数据库查询
  • 任何耗时操作

1.3 核心组件概览

组件 角色 使用场景
std::future 结果接收者 获取异步操作的结果
std::promise 结果提供者 在一个线程中设置结果,另一个线程获取
std::packaged_task 任务包装器 将函数包装为异步任务
std::async 任务启动器 最简单的启动异步任务的方式

关系图:

复制代码
std::async → 返回 → std::future
std::promise → 创建 → std::future
std::packaged_task → 获取 → std::future

2. std::future - 异步结果的接收者

2.1 基本概念

std::future 是一个模板类,代表一个异步操作的未来结果。你可以把它想象成一张"提货单":

  • 现在可能还没有结果
  • 将来某个时间点结果会准备好
  • 可以随时查询结果是否就绪
  • 可以等待并获取结果

核心特性:

  • 只能移动,不能复制
  • get() 只能调用一次
  • 自动等待异步任务完成

2.2 主要方法

方法 功能 说明
get() 获取结果 阻塞直到结果就绪,只能调用一次
wait() 等待完成 阻塞直到结果就绪,但不获取结果
wait_for(duration) 超时等待 等待指定时间,返回状态
wait_until(time_point) 等到某时间点 等到指定时间点,返回状态
valid() 检查有效性 检查是否有共享状态

wait_for 返回值:

  • std::future_status::ready - 结果已就绪
  • std::future_status::timeout - 超时
  • std::future_status::deferred - 任务尚未启动(延迟执行)

2.3 代码示例

示例1:基本使用 - 这是最常用的方法
cpp 复制代码
#include <iostream>
#include <future>
#include <thread>
#include <chrono>

int compute_square(int x) {
    std::cout << "计算 " << x << " 的平方..." << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(2));  // 模拟耗时操作
    return x * x;
}

int main() {
    std::cout << "启动异步任务" << std::endl;
    
    // 启动异步任务,返回 future
    std::future<int> result = std::async(std::launch::async, compute_square, 10);
    
    std::cout << "主线程可以继续做其他事..." << std::endl;
    
    // 可以做其他事情
    std::this_thread::sleep_for(std::chrono::seconds(1));
    std::cout << "做了一些其他工作" << std::endl;
    
    // 获取结果(如果还没完成会等待)
    std::cout << "等待结果..." << std::endl;
    int value = result.get();
    
    std::cout << "结果: " << value << std::endl;
    
    return 0;
}
示例2:超时等待
cpp 复制代码
#include <iostream>
#include <future>
#include <chrono>

int slow_computation() {
    std::this_thread::sleep_for(std::chrono::seconds(3));
    return 42;
}

int main() {
    std::future<int> result = std::async(std::launch::async, slow_computation);
    
    std::cout << "等待结果(最多2秒)..." << std::endl;
    
    // 等待最多2秒
    std::future_status status = result.wait_for(std::chrono::seconds(2));
    
    if (status == std::future_status::ready) {
        std::cout << "结果已就绪: " << result.get() << std::endl;
    } else if (status == std::future_status::timeout) {
        std::cout << "超时!任务还在运行中..." << std::endl;
        // 继续等待
        std::cout << "继续等待..." << std::endl;
        int value = result.get();  // 会阻塞直到完成
        std::cout << "最终结果: " << value << std::endl;
    }
    
    return 0;
}
示例3:检查有效性
cpp 复制代码
#include <iostream>
#include <future>

int main() {
    std::future<int> f1;
    std::cout << "f1 有效吗? " << (f1.valid() ? "是" : "否") << std::endl;
    
    std::future<int> f2 = std::async(std::launch::async, []{ return 42; });
    std::cout << "f2 有效吗? " << (f2.valid() ? "是" : "否") << std::endl;
    
    int result = f2.get();
    std::cout << "结果: " << result << std::endl;
    
    // get() 后 future 变为无效
    std::cout << "get() 后 f2 有效吗? " << (f2.valid() ? "是" : "否") << std::endl;
    
    return 0;
}

3. std::promise - 异步结果的提供者

3.1 基本概念

std::promise 是结果的提供者 ,它允许你在一个线程中设置值或异常,然后在另一个线程中通过 std::future 获取。

promise 和 future 的关系:

复制代码
线程A: promise.set_value(42)  →  共享状态  →  线程B: future.get() 返回 42

核心特性:

  • 一次性使用:只能设置一次值
  • 配对使用:promise 和 future 配对
  • 异常传递:可以传递异常

3.2 使用方法

基本步骤:

  1. 创建 std::promise<T> 对象
  2. 通过 get_future() 获取关联的 std::future
  3. 将 promise 传递给工作线程(使用 std::ref()
  4. 工作线程调用 set_value()set_exception()
  5. 主线程通过 future 的 get() 获取结果

3.3 代码示例

示例1:基本使用
cpp 复制代码
#include <iostream>
#include <future>
#include <thread>
#include <chrono>

void compute(std::promise<int>& prom, int x) {
    std::cout << "工作线程:开始计算..." << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(2));
    
    int result = x * x;
    std::cout << "工作线程:计算完成,设置结果" << std::endl;
    
    prom.set_value(result);  // 设置结果
}

int main() {
    std::promise<int> prom;
    std::future<int> fut = prom.get_future();  // 获取 future
    
    // 启动线程,注意使用 std::ref()
    std::thread t(compute, std::ref(prom), 10);
    
    std::cout << "主线程:等待结果..." << std::endl;
    
    // 获取结果(会阻塞直到 set_value 被调用)
    int result = fut.get();
    
    std::cout << "主线程:得到结果 = " << result << std::endl;
    
    t.join();
    
    return 0;
}
示例2:传递异常
cpp 复制代码
#include <iostream>
#include <future>
#include <thread>
#include <stdexcept>

void risky_operation(std::promise<int>& prom, int x) {
    try {
        if (x < 0) {
            throw std::invalid_argument("参数不能为负数!");
        }
        
        std::this_thread::sleep_for(std::chrono::seconds(1));
        prom.set_value(x * x);
        
    } catch (...) {
        // 捕获异常并通过 promise 传递
        prom.set_exception(std::current_exception());
    }
}

int main() {
    std::promise<int> prom;
    std::future<int> fut = prom.get_future();
    
    // 传递负数,会触发异常
    std::thread t(risky_operation, std::ref(prom), -5);
    
    try {
        int result = fut.get();  // 这里会重新抛出异常
        std::cout << "结果: " << result << std::endl;
    } catch (const std::exception& e) {
        std::cout << "捕获到异常: " << e.what() << std::endl;
    }
    
    t.join();
    
    return 0;
}
示例3:多个线程等待同一个结果(使用 shared_future)
cpp 复制代码
#include <iostream>
#include <future>
#include <thread>
#include <vector>

void wait_for_signal(std::shared_future<void> fut, int id) {
    std::cout << "线程 " << id << " 等待信号..." << std::endl;
    fut.get();  // 等待信号
    std::cout << "线程 " << id << " 收到信号,开始工作!" << std::endl;
}

int main() {
    std::promise<void> signal;
    std::shared_future<void> ready = signal.get_future().share();
    
    std::vector<std::thread> threads;
    
    // 创建多个等待线程
    for (int i = 0; i < 5; ++i) {
        threads.emplace_back(wait_for_signal, ready, i);
    }
    
    std::cout << "主线程:准备中..." << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(2));
    
    std::cout << "主线程:发送信号!" << std::endl;
    signal.set_value();  // 释放所有等待线程
    
    for (auto& t : threads) {
        t.join();
    }
    
    return 0;
}

4. std::packaged_task - 可调用对象的包装器

4.1 基本概念

std::packaged_task 是一个可调用对象的包装器,它将函数、lambda 或函数对象包装成一个异步任务,可以在任何时候执行。

与 promise 的区别:

  • promise:手动设置值
  • packaged_task:自动从函数返回值设置

4.2 使用方法

基本步骤:

  1. 创建 std::packaged_task<返回类型(参数类型)> 对象
  2. 通过 get_future() 获取关联的 future
  3. 像普通函数一样调用 task(可以在任何线程)
  4. 通过 future 获取结果

4.3 代码示例

示例1:基本使用
cpp 复制代码
#include <iostream>
#include <future>
#include <thread>

int multiply(int a, int b) {
    std::cout << "执行乘法: " << a << " * " << b << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(1));
    return a * b;
}

int main() {
    // 创建 packaged_task,包装函数
    std::packaged_task<int(int, int)> task(multiply);
    
    // 获取 future
    std::future<int> result = task.get_future();
    
    // 在新线程中执行任务(注意:task 是 move-only)
    std::thread t(std::move(task), 6, 7);//std::packaged_task 是只能移动(move-only)的类型,不能被复制
    
    std::cout << "主线程:等待结果..." << std::endl;
    
    // 获取结果
    std::cout << "结果: " << result.get() << std::endl;
    
    t.join();
    
    return 0;
}
示例2:使用 Lambda 表达式
cpp 复制代码
#include <iostream>
#include <future>
#include <thread>

int main() {
    // 使用 lambda 创建 packaged_task
    std::packaged_task<int(int, int)> task([](int a, int b) {
        std::cout << "Lambda 计算: " << a << " + " << b << std::endl;
        return a + b;
    });
    
    std::future<int> result = task.get_future();
    
    // 在当前线程执行(不一定要在新线程)
    task(10, 20);
    
    std::cout << "结果: " << result.get() << std::endl;
    
    return 0;
}
示例3:任务队列(实际应用场景)
cpp 复制代码
#include <iostream>
#include <future>
#include <queue>
#include <thread>
#include <mutex>
#include <functional>

class TaskQueue {
private:
    std::queue<std::packaged_task<int()>> tasks_;
    std::mutex mtx_;
    std::thread worker_;
    bool stop_ = false;
    
    void worker_thread() {
        while (true) {
            std::packaged_task<int()> task;
            
            {
                std::lock_guard<std::mutex> lock(mtx_);
                if (stop_ && tasks_.empty()) {
                    break;
                }
                if (!tasks_.empty()) {
                    task = std::move(tasks_.front());
                    tasks_.pop();
                }
            }
            
            if (task.valid()) {
                task();  // 执行任务
            } else {
                std::this_thread::sleep_for(std::chrono::milliseconds(10));
            }
        }
    }
    
public:
    TaskQueue() : worker_(&TaskQueue::worker_thread, this) {}
    
    ~TaskQueue() {
        {
            std::lock_guard<std::mutex> lock(mtx_);
            stop_ = true;
        }
        worker_.join();
    }
    
    // 提交任务,返回 future
    std::future<int> submit(std::function<int()> func) {
        std::packaged_task<int()> task(func);
        std::future<int> result = task.get_future();
        
        {
            std::lock_guard<std::mutex> lock(mtx_);
            tasks_.push(std::move(task));
        }
        
        return result;
    }
};

int main() {
    TaskQueue queue;
    
    // 提交多个任务
    auto f1 = queue.submit([]{ 
        std::cout << "任务1执行" << std::endl;
        return 100; 
    });
    
    auto f2 = queue.submit([]{ 
        std::cout << "任务2执行" << std::endl;
        return 200; 
    });
    
    auto f3 = queue.submit([]{ 
        std::cout << "任务3执行" << std::endl;
        return 300; 
    });
    
    // 获取结果
    std::cout << "任务1结果: " << f1.get() << std::endl;
    std::cout << "任务2结果: " << f2.get() << std::endl;
    std::cout << "任务3结果: " << f3.get() << std::endl;
    
    return 0;
}

5. std::async - 最简单的异步任务启动

5.1 基本概念

std::async 是最简单、最常用的启动异步任务的方式。它会自动创建线程(或复用线程),并返回一个 std::future 对象。

优势:

  • 简单:一行代码启动异步任务
  • 自动管理:自动管理线程生命周期
  • 灵活:可以选择同步或异步执行
  • 异常安全:自动传递异常

5.2 启动策略

策略 说明 何时执行
std::launch::async 异步执行 立即在新线程中执行
std::launch::deferred 延迟执行 调用 get()wait() 时在当前线程执行
`std::launch::async std::launch::deferred` 自动选择(默认)

5.3 代码示例

示例1:基本使用
cpp 复制代码
#include <iostream>
#include <future>
#include <chrono>

int compute(int x) {
    std::cout << "计算中..." << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(2));
    return x * x;
}

int main() {
    std::cout << "启动异步任务" << std::endl;
    
    // 最简单的方式:使用 async
    std::future<int> result = std::async(std::launch::async, compute, 10);
    
    std::cout << "做其他工作..." << std::endl;
    
    // 获取结果
    std::cout << "结果: " << result.get() << std::endl;
    
    return 0;
}
示例2:启动策略对比
cpp 复制代码
#include <iostream>
#include <future>
#include <thread>

void print_thread_id(const std::string& label) {
    std::cout << label << " 线程ID: " 
              << std::this_thread::get_id() << std::endl;
}

int task() {
    print_thread_id("任务执行在");
    return 42;
}

int main() {
    print_thread_id("主线程");
    
    // 异步执行(新线程)
    std::cout << "\n--- std::launch::async ---" << std::endl;
    auto f1 = std::async(std::launch::async, task);
    f1.get();
    
    // 延迟执行(当前线程)
    std::cout << "\n--- std::launch::deferred ---" << std::endl;
    auto f2 = std::async(std::launch::deferred, task);
    std::cout << "还没有执行任务..." << std::endl;
    f2.get();  // 这时才在当前线程执行
    
    return 0;
}
示例3:并发执行多个任务
cpp 复制代码
#include <iostream>
#include <future>
#include <vector>
#include <chrono>
#include <numeric>

// 计算数组部分和
int partial_sum(const std::vector<int>& data, size_t start, size_t end) {
    std::cout << "计算 [" << start << ", " << end << ") 的和" << std::endl;
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
    return std::accumulate(data.begin() + start, data.begin() + end, 0);
}

int main() {
    // 创建大数组
    std::vector<int> data(10000);
    for (size_t i = 0; i < data.size(); ++i) {
        data[i] = i + 1;
    }
    
    auto start_time = std::chrono::high_resolution_clock::now();
    
    // 将工作分成4个任务并发执行
    size_t chunk_size = data.size() / 4;
    
    std::vector<std::future<int>> futures;
    for (int i = 0; i < 4; ++i) {
        size_t start = i * chunk_size;
        size_t end = (i == 3) ? data.size() : (i + 1) * chunk_size;
        
        futures.push_back(std::async(std::launch::async, 
                                     partial_sum, 
                                     std::ref(data),  // 注意:使用 std::ref()
                                     start, 
                                     end));
    }
    
    // 收集结果
    int total = 0;
    for (auto& f : futures) {
        total += f.get();
    }
    
    auto end_time = std::chrono::high_resolution_clock::now();
    auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time);
    
    std::cout << "总和: " << total << std::endl;
    std::cout << "用时: " << duration.count() << " ms" << std::endl;
    
    return 0;
}
示例4:异常处理
cpp 复制代码
#include <iostream>
#include <future>
#include <stdexcept>

int divide(int a, int b) {
    if (b == 0) {
        throw std::runtime_error("除数不能为0!");
    }
    return a / b;
}

int main() {
    // 正常情况
    try {
        auto f1 = std::async(std::launch::async, divide, 10, 2);
        std::cout << "10 / 2 = " << f1.get() << std::endl;
    } catch (const std::exception& e) {
        std::cout << "错误: " << e.what() << std::endl;
    }
    
    // 异常情况
    try {
        auto f2 = std::async(std::launch::async, divide, 10, 0);
        std::cout << "10 / 0 = " << f2.get() << std::endl;  // 这里会抛出异常
    } catch (const std::exception& e) {
        std::cout << "捕获异常: " << e.what() << std::endl;
    }
    
    return 0;
}

6. 四者对比与选择

6.1 特性对比

特性 std::async std::promise std::packaged_task std::future
作用 启动异步任务 手动设置结果 包装可调用对象 获取结果
返回值 返回 future 创建 future 获取 future -
易用性 ⭐⭐⭐⭐⭐ 最简单 ⭐⭐⭐ 需要手动设置 ⭐⭐⭐⭐ 较简单 ⭐⭐⭐⭐⭐ 透明
灵活性 ⭐⭐⭐ 中等 ⭐⭐⭐⭐⭐ 最灵活 ⭐⭐⭐⭐ 灵活 ⭐⭐ 只读
线程管理 自动 手动 手动 -
典型用途 简单异步任务 复杂同步场景 任务队列、延迟执行 获取异步结果

6.2 使用场景建议

使用 std::async 的场景:
cpp 复制代码
// ✅ 简单的异步计算
auto result = std::async(std::launch::async, compute_something, arg);

// ✅ 快速并发多个任务
std::vector<std::future<int>> results;
for (int i = 0; i < 10; ++i) {
    results.push_back(std::async(std::launch::async, task, i));
}

适用于:

  • 简单的一次性异步任务
  • 快速原型开发
  • 不需要精细控制线程的场景
使用 std::promise 的场景:
cpp 复制代码
// ✅ 需要在任意时刻设置结果
void worker(std::promise<Result>& prom) {
    // ... 复杂逻辑
    if (condition) {
        prom.set_value(result);
    } else {
        prom.set_exception(...);
    }
}

// ✅ 线程间信号通知
std::promise<void> ready_signal;
// 多个线程等待信号...
ready_signal.set_value();  // 释放所有等待线程

适用于:

  • 需要在任意位置设置结果
  • 复杂的线程同步逻辑
  • 一对多的信号通知
使用 std::packaged_task 的场景:
cpp 复制代码
// ✅ 任务队列
std::queue<std::packaged_task<int()>> task_queue;

// ✅ 延迟执行
std::packaged_task<int()> task(compute);
auto future = task.get_future();
// ... 稍后再执行
task();

// ✅ 线程池
void thread_pool_worker() {
    while (true) {
        auto task = get_task_from_queue();
        task();  // 执行任务
    }
}

适用于:

  • 任务队列系统
  • 线程池实现
  • 需要延迟执行的任务

7. 实际应用场景

场景1:并发下载文件

cpp 复制代码
#include <iostream>
#include <future>
#include <vector>
#include <string>
#include <chrono>

// 模拟下载文件
std::string download_file(const std::string& url) {
    std::cout << "开始下载: " << url << std::endl;
    
    // 模拟网络延迟
    std::this_thread::sleep_for(std::chrono::seconds(2));
    
    std::cout << "下载完成: " << url << std::endl;
    return "Content of " + url;
}

int main() {
    std::vector<std::string> urls = {
        "http://example.com/file1.txt",
        "http://example.com/file2.txt",
        "http://example.com/file3.txt",
        "http://example.com/file4.txt"
    };
    
    auto start = std::chrono::high_resolution_clock::now();
    
    // 并发下载所有文件
    std::vector<std::future<std::string>> futures;
    for (const auto& url : urls) {
        futures.push_back(std::async(std::launch::async, download_file, url));
    }
    
    // 收集所有结果
    std::vector<std::string> contents;
    for (auto& f : futures) {
        contents.push_back(f.get());
    }
    
    auto end = std::chrono::high_resolution_clock::now();
    auto duration = std::chrono::duration_cast<std::chrono::seconds>(end - start);
    
    std::cout << "\n全部下载完成!" << std::endl;
    std::cout << "总共用时: " << duration.count() << " 秒" << std::endl;
    std::cout << "(如果顺序下载需要 " << urls.size() * 2 << " 秒)" << std::endl;
    
    return 0;
}

场景2:数据库查询与UI更新

cpp 复制代码
#include <iostream>
#include <future>
#include <thread>
#include <string>

// 模拟数据库查询(耗时操作)
std::string query_database(const std::string& sql) {
    std::cout << "执行查询: " << sql << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(3));
    return "查询结果数据...";
}

// 模拟UI更新
void update_ui(const std::string& data) {
    std::cout << "UI更新: " << data << std::endl;
}

int main() {
    std::cout << "应用启动" << std::endl;
    
    // 异步执行数据库查询,不阻塞主线程
    auto result = std::async(std::launch::async, 
                             query_database, 
                             "SELECT * FROM users");
    
    // 主线程可以继续响应用户操作
    std::cout << "主线程保持响应,可以处理用户输入..." << std::endl;
    for (int i = 1; i <= 3; ++i) {
        std::cout << "  处理用户事件 " << i << std::endl;
        std::this_thread::sleep_for(std::chrono::seconds(1));
    }
    
    // 查询完成后更新UI
    std::string data = result.get();
    update_ui(data);
    
    return 0;
}

场景3:生产者-消费者(使用 promise)

cpp 复制代码
#include <iostream>
#include <future>
#include <thread>
#include <queue>
#include <mutex>

std::mutex mtx;
std::queue<int> data_queue;

void producer(std::promise<void>& done_signal) {
    for (int i = 0; i < 10; ++i) {
        {
            std::lock_guard<std::mutex> lock(mtx);
            data_queue.push(i);
            std::cout << "生产: " << i << std::endl;
        }
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
    
    // 生产完成,发送信号
    done_signal.set_value();
}

void consumer(std::future<void>& done_signal) {
    while (done_signal.wait_for(std::chrono::milliseconds(0)) != std::future_status::ready) {
        std::lock_guard<std::mutex> lock(mtx);
        if (!data_queue.empty()) {
            int value = data_queue.front();
            data_queue.pop();
            std::cout << "  消费: " << value << std::endl;
        }
        std::this_thread::sleep_for(std::chrono::milliseconds(150));
    }
    
    // 处理剩余数据
    std::lock_guard<std::mutex> lock(mtx);
    while (!data_queue.empty()) {
        int value = data_queue.front();
        data_queue.pop();
        std::cout << "  消费(剩余): " << value << std::endl;
    }
}

int main() {
    std::promise<void> done_signal;
    std::future<void> done_future = done_signal.get_future();
    
    std::thread prod(producer, std::ref(done_signal));
    std::thread cons(consumer, std::ref(done_future));
    
    prod.join();
    cons.join();
    
    std::cout << "全部完成" << std::endl;
    
    return 0;
}

8. 最佳实践

8.1 总是检查 future 的有效性

cpp 复制代码
// ✅ 好的做法
std::future<int> result = std::async(task);
if (result.valid()) {
    int value = result.get();
}

// ❌ 不好的做法
std::future<int> result;
int value = result.get();  // 未定义行为!

8.2 只调用 get() 一次

cpp 复制代码
// ❌ 错误:get() 只能调用一次
std::future<int> result = std::async(task);
int v1 = result.get();
int v2 = result.get();  // 错误!future 已经无效

// ✅ 正确:保存结果
std::future<int> result = std::async(task);
int value = result.get();
// 使用 value...

8.3 使用 shared_future 实现多次获取

cpp 复制代码
// ✅ 需要多次获取结果时使用 shared_future
std::future<int> fut = std::async(task);
std::shared_future<int> shared_fut = fut.share();

int v1 = shared_fut.get();  // OK
int v2 = shared_fut.get();  // OK,可以多次调用

8.4 注意 future 的析构行为

cpp 复制代码
// ⚠️ 注意:async 返回的 future 析构时会阻塞
{
    std::async(std::launch::async, long_task);
}  // 这里会阻塞等待 long_task 完成!

// ✅ 如果想要"发射后不管",保存 future
auto result = std::async(std::launch::async, long_task);
// ... 做其他事情
// 需要时再 get()

8.5 传递引用时使用 std::ref()

cpp 复制代码
void process(Data& data) {
    // ...
}

Data data;

// ❌ 错误:会尝试复制 data
// std::async(process, data);

// ✅ 正确:使用 std::ref()
std::async(std::launch::async, process, std::ref(data));

8.6 异常处理

cpp 复制代码
// ✅ 异常会通过 future 传递
try {
    auto result = std::async([]{ 
        throw std::runtime_error("出错了!");
        return 42;
    });
    
    int value = result.get();  // 在这里重新抛出异常
    
} catch (const std::exception& e) {
    std::cout << "捕获异常: " << e.what() << std::endl;
}

8.7 选择合适的启动策略

cpp 复制代码
// 🔧 根据需求选择策略

// 明确需要并发执行
auto f1 = std::async(std::launch::async, task);

// 可以延迟到需要时执行(节省资源)
auto f2 = std::async(std::launch::deferred, task);

// 让系统决定(默认)
auto f3 = std::async(task);

9. 常见问题

Q1: std::async、std::thread 该用哪个?

答:

场景 推荐 原因
需要返回值 std::async 自动通过 future 返回结果
不需要返回值 std::thread 更直接,开销稍小
简单任务 std::async 代码更简洁
需要精确控制线程 std::thread 更灵活
异常处理 std::async 异常自动传递

Q2: promise 只能设置一次值吗?

答: 是的。如果尝试多次设置,会抛出 std::future_error 异常:

cpp 复制代码
std::promise<int> prom;
prom.set_value(42);     // OK
prom.set_value(100);    // 抛出异常!

Q3: 为什么 async 有时不创建新线程?

答: 使用默认策略或 std::launch::deferred 时,任务可能延迟执行:

cpp 复制代码
auto f = std::async(task);  // 默认策略,可能延迟

// 解决方案:明确指定 async
auto f = std::async(std::launch::async, task);  // 强制异步

Q4: future.get() 超时了怎么办?

答: get() 没有超时版本,但可以用 wait_for()

cpp 复制代码
std::future<int> result = std::async(task);

if (result.wait_for(std::chrono::seconds(5)) == std::future_status::ready) {
    int value = result.get();  // 不会阻塞
} else {
    std::cout << "超时!" << std::endl;
    // 任务仍在运行...
}

Q5: packaged_task 和 promise 有什么区别?

答:

cpp 复制代码
// promise:手动设置
std::promise<int> prom;
prom.set_value(42);

// packaged_task:自动从函数返回值设置
std::packaged_task<int()> task([]{ return 42; });
task();  // 自动设置结果

// 选择:
// - 需要灵活控制时间和条件 → promise
// - 包装函数/lambda → packaged_task

Q6: 如何取消一个异步任务?

答: C++ 标准库不直接支持取消,需要自己实现:

cpp 复制代码
#include <atomic>

std::atomic<bool> should_stop{false};

void cancellable_task() {
    for (int i = 0; i < 1000; ++i) {
        if (should_stop.load()) {
            std::cout << "任务被取消" << std::endl;
            return;
        }
        // 做一些工作...
    }
}

int main() {
    auto result = std::async(std::launch::async, cancellable_task);
    
    std::this_thread::sleep_for(std::chrono::seconds(1));
    should_stop.store(true);  // 请求取消
    
    result.wait();
    return 0;
}

编译和运行

编译示例代码

bash 复制代码
# 基本编译(需要 C++11 支持)
g++ -std=c++11 -pthread example.cpp -o example

# 启用优化
g++ -std=c++11 -pthread -O2 example.cpp -o example

# C++17(支持更多特性)
g++ -std=c++17 -pthread example.cpp -o example

运行示例

bash 复制代码
./example

参考资料

官方文档

推荐书籍


总结

核心要点

  1. std::future - 异步结果的接收者

    • 像"提货单",代表未来的结果
    • get() 获取结果(只能调用一次)
    • wait_for() 实现超时等待
  2. std::promise - 异步结果的提供者

    • 手动设置结果或异常
    • 适合复杂的线程同步场景
    • 与 future 配对使用
  3. std::packaged_task - 可调用对象的包装器

    • 将函数包装成异步任务
    • 自动从返回值设置结果
    • 适合任务队列和线程池
  4. std::async - 最简单的异步启动方式

    • 一行代码启动异步任务
    • 自动管理线程
    • 首选方案(除非需要精细控制)

选择指南

复制代码
需要异步执行任务?
    ├─ 简单任务,需要返回值
    │   └─> 使用 std::async ⭐ 推荐
    │
    ├─ 需要在任意时刻设置结果
    │   └─> 使用 std::promise
    │
    ├─ 需要包装函数,延迟执行
    │   └─> 使用 std::packaged_task
    │
    └─ 只需要获取结果
        └─> 使用 std::future

最佳实践回顾

  • ✅ 优先使用 std::async(最简单)
  • ✅ 检查 future.valid() 再调用 get()
  • get() 只能调用一次
  • ✅ 传递引用时使用 std::ref()
  • ✅ 使用 wait_for() 实现超时
  • ✅ 异常会自动通过 future 传递
  • ✅ 需要多次获取结果用 shared_future

希望这份指南能帮助您掌握C++异步编程,编写高效、响应迅速的多线程程序!

相关推荐
小龙报2 小时前
《数组和函数的实践游戏---扫雷游戏(基础版附源码)》
c语言·开发语言·windows·游戏·创业创新·学习方法·visual studio
又是忙碌的一天2 小时前
Java基础 与运算
java·开发语言
liu****2 小时前
笔试强训(八)
开发语言·算法·1024程序员节
程序猫.2 小时前
学生管理系统
java·1024程序员节
m0_748241233 小时前
Java注解与反射实现日志与校验
java·开发语言·python
nianniannnn3 小时前
Qt布局管理停靠窗口QDockWidget类
开发语言·数据库·c++·qt·qt5·qt6.3
一成码农3 小时前
3w字一文讲透Java IO
java·开发语言
Yeats_Liao3 小时前
Go Web 编程快速入门 07.4 - 模板(4):组合模板与逻辑控制
开发语言·后端·golang
木易 士心3 小时前
MyBatis 与 Spring Data JPA 核心对比:选型指南与最佳实践
java·spring·1024程序员节