目 录CONTENT

文章目录

C++并发编程:std::async与std::future

TalentQ
2025-07-28 / 0 评论 / 0 点赞 / 4 阅读 / 0 字

引言

随着多核处理器的普及,C++程序员越来越需要利用并发编程来提升程序性能和响应能力。C++11标准引入了丰富的并发支持,其中std::asyncstd::future是最常用的异步任务和结果获取机制。

一、std::future与std::promise

1.1 什么是std::future?

std::future是C++11标准库提供的一个模板类,用于保存异步任务的结果。它可以看作是“未来某个时间可用的值”的占位符。通过std::future对象,主线程可以等待或获取异步任务的返回值。

1.2 什么是std::promise?

std::promise则是用于在一个线程中设置值,并让另一个线程通过std::future获取该值的机制。它们通常成对出现,适用于自定义线程间通信。

1.3 std::async的作用

std::async是C++11标准库中用于启动异步任务的函数模板。它自动将一个可调用对象(函数、lambda、成员函数等)放到新的线程或延迟执行,并返回一个std::future对象,用于获取结果。

二、std::async的用法详解

2.1 基本用法

#include <iostream>
#include <future>

// 计算阶乘的函数
int Factorial(int n) {
  int result = 1;
  for (int i = 2; i <= n; ++i) {
    result *= i;
  }
  return result;
}

int main() {
  // 启动异步任务,计算5的阶乘
  std::future<int> fut = std::async(Factorial, 5);

  // 在这里可以做其他事情...

  // 获取异步任务的结果(阻塞直到完成)
  int result = fut.get();
  std::cout << "5! = " << result << std::endl;
  return 0;
}

说明

  • std::async会立即启动一个异步任务,并返回一个std::future对象。

  • 调用future.get()会阻塞当前线程,直到异步任务完成并返回结果。

2.2 std::launch策略

std::async允许通过std::launch参数指定任务的启动策略:

  • std::launch::async:强制在新线程中异步执行。

  • std::launch::deferred:延迟执行,只有在调用get()wait()时才执行,且在调用线程中运行。

  • std::launch::async | std::launch::deferred(默认):由实现决定。

// 强制异步执行
auto fut1 = std::async(std::launch::async, Factorial, 6);

// 强制延迟执行
auto fut2 = std::async(std::launch::deferred, Factorial, 7);

注意事项

  • 使用deferred策略时,任务不会在后台线程中运行,而是在调用get()wait()时同步执行。

  • 如果不指定策略,编译器和运行库会根据实现自行决定。

2.3 结果获取与异常传递

获取结果

  • future.get():获取异步任务的返回值,若尚未完成则阻塞。

  • future.wait():阻塞直到任务完成,但不获取结果。

  • future.wait_for()future.wait_until():带超时等待。

异常传递

如果异步任务抛出异常,调用get()时会重新抛出该异常。

#include <stdexcept>

int ThrowException() {
  throw std::runtime_error("Something went wrong!");
}

int main() {
  std::future<int> fut = std::async(ThrowException);
  try {
    int result = fut.get();  // 这里会抛出异常
  } catch (const std::exception& e) {
    std::cout << "Caught exception: " << e.what() << std::endl;
  }
}

三、std::future与std::promise的配合

在自定义线程中,std::promisestd::future经常搭配使用,实现线程间的数据传递。

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

// 线程函数,通过promise设置结果
void CalculateFactorial(std::promise<int> prom, int n) {
  int result = 1;
  for (int i = 2; i <= n; ++i) {
    result *= i;
  }
  prom.set_value(result);  // 设置结果
}

int main() {
  std::promise<int> prom;
  std::future<int> fut = prom.get_future();
  std::thread t(CalculateFactorial, std::move(prom), 8);

  // 这里可以做其他事情...

  int result = fut.get();
  std::cout << "8! = " << result << std::endl;
  t.join();
}

四、适用场景与原理分析

4.1 适用场景

  • 异步计算任务:如网络请求、磁盘IO、复杂计算等,主线程无需等待即可继续执行。

  • 任务结果同步:主线程可通过future等待或获取子任务结果。

  • 异常安全:异步任务中的异常可自动传递到主线程。

4.2 原理简析

  • std::async底层会根据策略选择是否创建新线程(通常用std::thread实现),或者延迟在调用线程中执行。

  • std::futurestd::promise底层通过共享状态(shared state)实现线程间通信。

  • future.get()被调用时,会等待共享状态就绪(即任务完成或异常抛出)。

4.3 与其他并发机制的对比

  • std::thread:需要手动管理线程生命周期,无法直接获取返回值或异常。

  • std::packaged_task:将可调用对象包装成任务,与future配合使用,但更底层。

  • std::async:自动管理线程、返回值和异常,适合简单并发场景。

五、扩展知识:std::shared_future与多线程结果共享

如果多个线程需要获取同一个异步结果,可以使用std::shared_future

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

void PrintResult(std::shared_future<int> sfut) {
  std::cout << "Result: " << sfut.get() << std::endl;
}

int main() {
  std::future<int> fut = std::async(Factorial, 9);
  std::shared_future<int> sfut = fut.share();

  // 所有线程都可以调用sfut.get()
  std::thread t1(PrintResult, sfut);
  std::thread t2(PrintResult, sfut);

  t1.join();
  t2.join();
}

六、常见问题与注意事项

  1. future.get()只能调用一次,后续调用会抛出std::future_error

  2. 避免资源泄漏:使用std::async时,不要忽略返回的future对象,否则任务异常或未完成时会导致程序异常终止。

  3. 线程数量受限:频繁创建线程可能导致资源耗尽,适合中小规模并发任务。大型并发建议使用线程池。

七、总结

  • std::asyncstd::future为C++并发编程提供了强大而安全的异步任务管理机制。

  • 合理选择启动策略和同步方式,能显著提升程序性能与结构清晰度。

  • 在实际开发中,应根据任务复杂度和资源情况,灵活选择并发工具。

0

评论区