C++笔记---并发支持库(future) - 详解

1. 异步编程

std::futurestd::async 是 C++11 异步编程的核心组件,定义在<future>头文件中:

  • std::future异步操作结果的 “句柄”,用于在未来获取异步任务的返回值或异常
  • std::async 是异步任务启动器,简化了异步任务的创建,无需手动管理线程和结果传递,直接返回 std::future。

两者结合可高效实现 “启动任务→主线程继续工作→按需获取结果” 的异步编程模式,替代了传统手动创建线程 + 全局变量 / 回调函数的繁琐方式。

2. std::async

std::async 函数模板,用于异步启动一个可调用对象(函数、lambda、仿函数等),并返回关联的 std::future,简化了 “线程创建 + 结果传递” 的流程。

// 显式指定启动策略
template 
std::future::type>
async(std::launch policy, Fn&& fn, Args&&... args);
// 默认策略(由实现决定)
template 
std::future::type>
async(Fn&& fn, Args&&... args);

std::async 的参数与 std::thread 构造函数的参数十分相似,但是多了一个代表异步策略的参数。

异步启动可调用对象有两种策略,由 policy 参数决定:

策略行为
std::launch::async强制异步:立即创建新线程执行任务,任务在独立线程中运行。
std::launch::deferred延迟执行:任务不会立即启动,直到调用 future::get()/future::wait() 时,才在当前线程中执行(无新线程);若永远不调用 get()/wait(),任务永不执行。
默认策略(不传递参数)实现可选择两种策略之一(通常根据系统资源动态决定,如线程池负载)。

例如:

#include 
#include 
#include 
using namespace std;
void func(const string& name)
{
	cout << name << "正在执行..." << endl;
}
int main()
{
	// 启动一个线程异步执行func
	auto f1 = async(launch::async, func, "异步任务");
	// 延迟执行func
	auto f2 = async(launch::deferred, func, "延迟任务");
	cout << "主线程任务执行中..." << endl;
	this_thread::sleep_for(chrono::seconds(3));
	cout << "主线程任务执行完毕,开始获取异步任务与延迟任务的返回值" << endl;
	// 阻塞等待异步任务执行完成,获得结果,相当于pthread中的join
	f1.get();
	// 在主线程中立即执行延迟任务,获得结果
	f2.get();
	return 0;
}

结果如下:

3. std::future

std::future<T> 模板类(T 为异步任务的返回类型,若任务无返回值则用 std::future<void>),表示一个尚未完成的异步操作的结果

主要成员函数:

函数功能
T get()

(1)获取异步任务结果(阻塞直到完成);

(2)任务抛异常时,get() 会重新抛出该异常;

(3)仅可调用一次(结果以移动的方式返回)。

bool valid() const

检查 future 是否关联到有效的异步操作(默认构造 /move 后 /get() 后均为 false)。建议在调用get之前先调用该函数检验。

void wait()阻塞当前线程,直到异步任务完成(仅等待,不获取结果)。
future_status wait_for(const chrono::duration& rel_time)阻塞指定时长,返回状态:
- future_status::ready:任务完成;
- future_status::timeout:超时,任务未完成;
- future_status::deferred:任务被延迟执行(未启动)。
future_status wait_until(const chrono::time_point& abs_time)阻塞直到指定时间点,返回值同 wait_for。
void swap(future& other)交换两个 future 的状态。
future(future&& other)移动构造(future 不可拷贝,仅可移动)。

注意事项:

  • 一个future对象的 get() 只能调用一次,调用后 future 变为无效状态(valid() 返回 false);
  • 若异步任务未完成,调用 get()/wait() 会阻塞当前线程,直到结果可用;
  • 异步任务抛出的异常会被捕获并存储,调用 get() 时重新抛出。例如:
    int risky_task() {
        throw std::runtime_error("异步任务出错!");
        return 42;
    }
    int main() {
        std::future fut = std::async(risky_task);
        try {
            int res = fut.get(); // 重新抛出任务中的异常
            std::cout << "结果:" << res << std::endl;
        } catch (const std::runtime_error& e) {
            std::cout << "捕获异常:" << e.what() << std::endl; // 输出:异步任务出错!
        }
        return 0;
    }
  • future对象的析构函数会阻塞等待异步任务完成。若临时的future对象没有被变量接收的话,就会导致主线程在该行阻塞等待异步任务完成。
    void long_task() {
        std::this_thread::sleep_for(std::chrono::seconds(5));
        std::cout << "异步任务完成" << std::endl;
    }
    int main() {
        std::async(std::launch::async, long_task); // 阻塞等待5秒
        // 其他任务
        // ...
        return 0;
    }

若需多次获取异步结果(如多线程共享结果),可使用 std::shared_future

  • 可拷贝,get() 可调用多次;
  • 可通过 std::future::share() 或移动构造得到:
    std::future fut = std::async([](){ return 42; });
    std::shared_future sfut = fut.share(); // fut 变为无效
    // 多线程可同时调用 sfut.get()

4. std::future 的其他数据源

std::future 的结果不仅可来自 std::async,还可通过 std::promise 或 std::packaged_task 手动关联,std::async 本质是对这两者的封装:

4.1 std::promise

std::promise 封装了 std::future。我们可以通过 get_future 获取 promise 对象内部的 future 对象,同时也可以使用其他成员函数对 future 对象写入值或异常

这些写入行为都会导致 future 对象就绪。

函数功能
get_future返回与 promise 对象关联的 future 对象(仅可调用一次)。
set_value向 future 对象写入值。
set_exception向 future 对象写入异常。
set_value_at_thread_exit在线程退出时向 future 对象写入值。
set_exception_at_thread_exit在线程退出时向 future 对象写入异常。

例如:

std::promise prom;
std::future fut = prom.get_future();
// 新线程中设置值
std::thread t([&prom]() {
    std::this_thread::sleep_for(std::chrono::seconds(2));
    prom.set_value(100); // 设置结果,future 可获取
});
std::cout << "promise 结果:" << fut.get() << std::endl; // 输出 100
t.join();

4.2 std::packaged_task

std::packaged_task 是 C++11 并发库中封装可调用对象并关联异步结果的模板类,核心作用是将 “可调用对象(函数、lambda、仿函数等)” 与 std::future 绑定。

相比于std::function,std::packaged_task对象在被调用后不会直接返回可调用对象的结果(或抛异常),而是将结果(或异常)保存到与其关联的future对象当中

与promise相同,可通过get_future接口获取与其关联的future对象。

主要成员函数:

函数功能
std::future<Ret> get_future()获取与当前 packaged_task 关联的 future;
仅可调用一次,二次调用抛出 std::future_error。
void operator()(Args... args)执行包装的可调用对象,传入参数 args;
执行结果会自动设置到关联的 future(正常结果 / 异常);
仅可调用一次(除非调用 reset() 重置),重复调用抛 std::future_error。
void make_ready_at_thread_exit(Args... args)延迟执行并设置结果:
1. 立即执行包装的任务;
2. 线程退出时才将结果写入 future;
避免 “结果写入后,局部变量析构” 导致的悬空引用。
void reset()重置 packaged_task 状态:
1. 放弃当前未完成的结果;
2. 重新绑定可调用对象(保留原对象);
重置后可再次调用 operator() 和 get_future()。
bool valid() const检查 packaged_task 是否关联有效的可调用对象(默认构造 /move 后 /reset 前可能为 false)。

我们可以使用packaged_task来实现线程池,下面是核心原理的极简示例:

#include 
#include 
#include 
#include 
using namespace std;
template 
void taskRunner(queue>& taskQueue, mutex& queueMtx, condition_variable& cv)
{
	while (true)
	{
		// 获取一个任务
		packaged_task task;
		{
			unique_lock lock(queueMtx);
			while (taskQueue.empty())
			{
				cv.wait(lock);
			}
			task = move(taskQueue.front());
			taskQueue.pop();
		}
		// 空任务表示退出
		if (!task.valid())
			break;
		// 执行任务
		task();
	}
}
int main()
{
	queue> taskQueue;
	queue> resQueue;
	mutex queueMtx;
	condition_variable cv;
	// 启动任务执行线程
	thread worker(taskRunner, ref(taskQueue), ref(queueMtx), ref(cv));
	// 生成并插入任务
	for (int i = 0; i < 5; i++)
	{
		packaged_task task([i]() { return i * i; });
		// 保存结果
		resQueue.emplace(task.get_future());
		// 插入任务
		{
			lock_guard lock(queueMtx);
			taskQueue.emplace(move(task));
		}
		// 唤醒任务执行线程
		cv.notify_one();
	}
	// 插入空任务表示结束
	{
		lock_guard lock(queueMtx);
		taskQueue.emplace();
		cv.notify_one();
	}
	worker.join();
	// 打印结果
	while (!resQueue.empty())
	{
		cout << "执行结果: " << resQueue.front().get() << endl;
		resQueue.pop();
	}
	return 0;
}
posted @ 2026-01-19 14:23  yangykaifa  阅读(4)  评论(0)    收藏  举报