C++ 异步编程全景:一次讲透 std::future详解
一句话导读:把「子线程算完、把结果带回来」这件事,从
mutex + condition_variable的样板代码,重构为一张「取货单」。图片里的 7 个组件,其实共用 1 个心智模型。
引子:一个「线程算完把结果带回来」的经典需求
假设你要在主线程里拿到子线程算出来的一个 int。在 C++11 之前,标准做法只有一条路:共享变量 + 互斥量 + 条件变量。
std::mutex mtx;
std::condition_variable cv;
bool ready = false;
int result = 0;
void worker() {
std::lock_guard<std::mutex> lk(mtx);
result = 42; // 算出来的结果
ready = true;
cv.notify_one();
}
int main() {
std::thread t(worker);
std::unique_lock<std::mutex> lk(mtx);
cv.wait(lk, [] { return ready; });
std::cout << result << std::endl; // 42
lk.unlock();
t.join();
}这段代码本身没错,但它暴露了三个真实痛点,而这三点正是 <future> 头文件要解决的问题:
- 样板代码太多。一个线程传回一个
int,就要写一个 mutex、一个条件变量、一个ready标志位。 - 异常传不回来。子线程里抛出的异常无法跨越线程边界,
std::terminate是唯一结局,主线程永远不知道发生了什么。 - 语义不清晰。
result是个共享变量,谁都能改,读写时序完全靠人脑推理。
C++11 给出的答案是:把「结果」当成一件快递,做成一张单子。生产者负责发货,消费者拿着单子取货,两者之间不需要共享任何业务变量。
这套设施一共有 7 个名字,也就是本文要讲的 7 个组件:
| # | 组件 | 所属头文件 |
|---|---|---|
| 1 | std::future | <future> |
| 2 | std::shared_future | <future> |
| 3 | std::promise | <future> |
| 4 | std::packaged_task | <future> |
| 5 | std::async | <future> |
| 6 | std::launch | <future> |
| 7 | std::future_status | <future> |
它们在图片里的原始描述,先放在这里备查:
- std::future:用于获取异步操作的结果,只能获取一次
- std::shared_future:类似于 future,但可以被多次获取
- std::promise:用于存储值或异常,可以通过 future 获取
- std::packaged_task:包装可调用对象,便于异步获取结果
- std::async:便捷地启动异步任务
- std::launch:指定异步任务的启动策略
- std::future_status:表示 future 的状态
一、先建立心智模型:三个角色,七件工具
⚠️ 本篇最重要的一段话,请务必读完再往下看。
不要把这 7 个名字当成 7 个孤立的知识点死记。它们其实只分成三个角色,角色分清楚了,后面所有细节都是顺理成章的推论。
| 角色 | 组件 | 干什么 |
|---|---|---|
| 结果消费者 | std::future、std::shared_future | 手里拿「取货单」的那一方,负责取货 |
| 结果生产者 | std::promise、std::packaged_task、std::async | 负责把结果「写进」共享状态 |
| 控制与状态 | std::launch、std::future_status | 怎么启动任务、现在到底好没好 |
而串起这三个角色的,是一个看不见的东西:共享状态(shared state)。
一份共享状态里只会存三种东西之一:
- 一个值
- 一个异常
- 「还没好」
future 和 promise 只是这份共享状态的两个端点:
std::promise<T> ────► [ 共享状态:值 / 异常 / 未就绪 ] ────► std::future<T> (发货端) (中间的中转仓) (取货端)
理解了这个模型,下面所有行为都可以自行推导出来:
- 为什么
get()会阻塞?因为中转仓里还空着,得等。 - 为什么
get()只能调用一次?因为取货端是「搬走」货物,搬完仓里就没了。 - 为什么
shared_future可以取多次?因为它取的是「复印件」,只看不搬。 - 为什么生产者要
set_exception?因为中转仓除了值,还能存异常。
下面逐个拆解。
二、std::future:只发一次的取货单
std::future<T> 是整个体系的「交付物」,它持有一份共享状态。它的所有行为,都由「独占 + 一次性」这两条性质推出来:
2.1 四条核心性质
- 不可拷贝,只能移动(
std::move)。取货单只有一张,交出所有权就没了。这也意味着std::future可以放进容器、可以作为返回值,但不能直接复制分发给多个线程。 get()阻塞直到就绪,然后「把货物搬走」。搬走了,单子就作废了 —— 调用后valid()立刻变为false。- 所以
get()只能有效调用一次。第二次调用属于未定义行为,主流实现(libstdc++)会抛std::future_error,错误码std::future_errc::no_state。 - 异常会被「搬」成抛出。生产者写入的是异常时,消费者
get()会在自己的线程里原样收到它 —— 这是它相对条件变量的最大优势。
2.2 最小可运行示例
#include <future>
#include <iostream>
#include <thread>
int main() {
std::promise<int> prom;
std::future<int> fut = prom.get_future();
std::thread t([&prom] {
prom.set_value(42); // 兑现承诺
});
int value = fut.get(); // 阻塞直到兑现,并且只能取一次
std::cout << value << std::endl;
t.join();
return 0;
}编译命令(顺手给你):
g++ -std=c++17 -pthread main.cpp -o main
2.3 验证「取一次就作废」
这段代码建议亲手跑一遍,印象会深得多:
std::future<int> f = std::async(std::launch::async, [] { return 7; });
std::cout << f.valid() << std::endl; // 1
std::cout << f.get() << std::endl; // 7
std::cout << f.valid() << std::endl; // 0:取货单已作废
// f.get(); // 未定义行为;libstdc++ 实际抛 std::future_error(no_state)2.4 等待的四种姿势(重点)
很多人只知道 get(),结果一等等到天荒地老。其实 future 提供了四个接口:
| 接口 | 是否阻塞 | 返回值 | 典型用途 |
|---|---|---|---|
get() | 阻塞到就绪 | T(结果) | 取结果,同时把异常抛出来 |
wait() | 阻塞到就绪 | void | 只要就绪,不要结果(不会抛异常) |
wait_for() | 最多阻塞一段时间 | future_status | 带超时的等待、轮询进度 |
wait_until() | 最多阻塞到某个时间点 | future_status | 截止时间型任务 |
⚠️ 一个高频误解:wait() 只等不取,异常不会在这里抛出。如果你想让异常正常传播,必须调用 get()。
2.5 C++20 的一个小提醒
std::future 不是 awaiter,co_await fut 在标准库里编译不过。想做协程异步,需要自己写 operator co_await 或者用第三方库(如 cppcoro / folly)。这一点常有人误解,提前说清楚。
三、std::shared_future:取货单的复印件
std::future 的问题是「独占」—— 一个结果只能给一个人。如果三个线程都要等同一个结果呢?
这时候用 std::shared_future<T>。它和 future 的区别只有两点,但极其关键:
- 可拷贝、可赋值,多个副本共享同一份共享状态,可以分发给任意多个线程。
get()可以调用任意多次,返回类型是const T&(而不是把值搬走的T)。正因为只是「看」而不是「搬走」,所以能反复取。
3.1 从 future 转换:必须用 share()
std::future<int> f = std::async(std::launch::async, [] { return 42; });
std::shared_future<int> sf = f.share(); // f 立刻失效,sf 可以随便拷贝
auto reader = [sf](const char* who) {
// get() 返回 const int&,多线程并发调用是安全的
std::cout << who << " -> " << sf.get() << std::endl;
};
std::thread a(reader, "A");
std::thread b(reader, "B");
a.join();
b.join();3.2 两个必须知道的细节
- 不能「拷贝 future 变成 shared_future」。
std::future本身不可拷贝,编译器直接报错。唯一正确的路径是share()(或移动构造std::shared_future<T> sf(std::move(f));)。 share()之后原future的valid()立刻变成false,别再碰它,否则就是no_state。
3.3 返回值类型的坑
shared_future<T>::get() 返回 const T&,是引用。所以:
const int& r = sf.get(); // 生命周期与 sf 绑定,不要让它活得比 sf 长
对于 shared_future<void>,get() 返回 void;对于 shared_future<T&>,返回 T&。
四、std::promise:手动兑现的承诺
std::promise 是生产端最底层的组件。语义非常朴素:我承诺将来给一个值,你现在先拿走一张取货单(get_future()),到时候我来兑现。
4.1 六条使用要点
get_future()对同一个promise只能调用一次,第二次抛std::future_error(future_already_retrieved)。因为一个承诺只对应一张单子。set_value()只能成功调用一次,重复调用抛std::future_error(promise_already_satisfied)。set_exception()把异常写进共享状态,消费者get()时在自己的线程里收到它 —— 这就是「跨线程抛异常」的标准做法。set_value_at_thread_exit()/set_exception_at_thread_exit():在线程真正退出时才兑现,常用于让线程局部对象(thread_local 变量)先安全析构,是模仿std::async语义的细节工具。promise不可拷贝、可移动,所以它非常适合std::move进 lambda 或线程里。
4.2 把异常也一起快递过去
std::promise<int> prom;
std::future<int> fut = prom.get_future();
std::thread t([&prom] {
try {
throw std::runtime_error("boom");
} catch (...) {
prom.set_exception(std::current_exception()); // 异常也一起快递过去
}
});
try {
fut.get(); // 结果没回来,异常先回来了
} catch (const std::exception& e) {
std::cout << "caught: " << e.what() << std::endl;
}
t.join();注意
std::current_exception()这个细节:它捕获的是当前正在处理的异常,在catch (...)块里调用是唯一正确的位置。
4.3 最经典的坑:broken promise
如果 promise 还没兑现就被析构了会怎样?
标准规定:共享状态被置为「破碎」,与之关联的 future 立刻就绪,get() 时抛出 std::future_error,错误码是 std::future_errc::broken_promise。
std::future<int> f;
{
std::promise<int> p;
f = p.get_future();
} // p 没兑现就析构了
try {
f.get(); // 抛 std::future_error
// code() == std::future_errc::broken_promise
} catch (const std::future_error& e) {
std::cout << e.what() << std::endl;
}这个设计非常友好:它让你不会永远卡死在一个永远不会来的结果上。这是「异常也走快递」这个设计的额外红利。
4.4 完整的 std::future_errc 枚举
| 错误码 | 触发条件 |
|---|---|
broken_promise | promise 未兑现就被销毁 |
future_already_retrieved | 重复调用 get_future() |
promise_already_satisfied | 重复调用 set_value() |
no_state | 对无效(已作废)的 future 调用 get()/wait() |
std::future_error 继承自 std::logic_error,所以用 catch (const std::logic_error&) 也能接住。
五、std::packaged_task:自带取货单的「可执行体」
promise 的问题是:你得自己写 try/catch,手动把结果塞进去,很啰嗦。
std::packaged_task 把这件事自动化了:你把一个可调用对象交给它,它帮你执行、帮你捕获返回值、帮你捕获异常,执行完自动写进共享状态。
5.1 关键点:模板参数是「函数签名」
std::packaged_task<int(int, int)> task([](int a, int b) { return a + b; });
// ^^^^^^^^^^^^ 这里是签名,不是 int
这是新手最容易写错的地方 —— 它和 std::function 的用法一致,参数是完整的函数类型。
5.2 基本用法
std::packaged_task<int(int, int)> task([](int a, int b) { return a + b; });
std::future<int> fut = task.get_future();
std::thread t(std::move(task), 1, 2); // task 只可 move,交给线程去执行
std::cout << fut.get() << std::endl; // 3
t.join();注意:task 本身是可调用对象,task(1, 2) 就能在当前线程直接执行。把它交给线程只是「换个地方执行」而已。
5.3 为什么它比 std::function 更适合线程池?
std::function 只能执行,执行完结果就丢了;packaged_task 执行完,结果会稳稳落到那张取货单里。
所以它天生适合放进任务队列 / 线程池:队列里存的是 packaged_task(已擦除类型),调用方拿到的是 future。
一个极简线程池骨架(这段看懂,你就彻底懂它了):
class ThreadPool {
public:
explicit ThreadPool(size_t n) {
for (size_t i = 0; i < n; ++i) {
workers_.emplace_back([this] {
for (;;) {
std::packaged_task<void()> job;
{
std::unique_lock<std::mutex> lk(mtx_);
cv_.wait(lk, [this] { return stop_ || !tasks_.empty(); });
if (stop_ && tasks_.empty()) return;
job = std::move(tasks_.front());
tasks_.pop();
}
job(); // 执行,结果自动写进对应 future
}
});
}
}
template <class F>
std::future<std::invoke_result_t<F>> submit(F&& f) {
using R = std::invoke_result_t<F>;
std::packaged_task<R()> task(std::forward<F>(f));
std::future<R> fut = task.get_future();
{
std::lock_guard<std::mutex> lk(mtx_);
tasks_.emplace(std::move(task)); // 类型擦除成 packaged_task<void()>
}
cv_.notify_one();
return fut;
}
private:
std::vector<std::thread> workers_;
std::queue<std::packaged_task<void()>> tasks_;
std::mutex mtx_;
std::condition_variable cv_;
bool stop_ = false;
};小提示:
std::packaged_task<void()>可以接收任意签名的packaged_task<R(Args...)>(通过移动构造的模板重载),这就是「类型擦除」的关键。实际工程里更常用std::function<void()>包一层,或者上 C++23 的std::move_only_function。
5.4 reset():可以重复使用
reset() 会丢弃旧的共享状态,创建一个全新的。很多人不知道这个接口:
std::packaged_task<int()> task([] { return 1; });
task(); // 直接当函数调用:立刻执行
std::cout << task.get_future().get() << std::endl; // 1
task.reset(); // 复位,拿到新的共享状态
task();
std::cout << task.get_future().get() << std::endl; // 15.5 调用 operator() 时抛出的异常不会外泄
packaged_task 执行内部逻辑时抛出的异常,会被它捕获并存入共享状态,而不是向调用者抛。所以你在线程里调用 task() 是安全的,异常会在 future.get() 处现身。
六、std::async 与 std::launch:最省事,也最容易踩坑
std::async 是三个生产工具的「一站式封装」:既帮你启动任务,又直接把 future 递给你。
auto fut = std::async(std::launch::async, [] { return heavy(); });
但它有一个必须先讲清楚的参数:启动策略 std::launch。策略选错,你写的「并发」可能一行都没并发。
6.1 三种策略
// 1) 强制异步:一定另起线程(或交给实现内部的线程池)
auto f1 = std::async(std::launch::async, [] { return heavy(); });
// 2) 延迟执行:一个线程都不开,等你取结果时才在「当前线程」跑
auto f2 = std::async(std::launch::deferred, [] { return heavy(); });
f2.get(); // 此刻才真正执行
// 3) 默认(async | deferred):实现自己挑,可能并发也可能不并发
auto f3 = std::async([] { return heavy(); });| 策略 | 含义 | 风险 |
|---|---|---|
launch::async | 必须新开线程(或线程池)执行 | 线程数不可控,注意别开爆 |
launch::deferred | 完全惰性,get()/wait() 时才在调用线程同步执行 | 不 get() 就永远不执行 |
async | deferred(默认) | 实现自由选择,甚至可以每次调用都换 | 行为不可预期,最坑 |
6.2 四个必须记住的坑
- 默认策略是
async | deferred。标准允许实现在两者间任意选择。也就是说,不写策略的std::async,并发行为是不可预期的。 deferred的任务不是「稍后自动执行」,而是「你不 get/wait 它,它永远不会执行」。这是「任务静默丢失」类 bug 的最大根源。deferred的执行发生在调用get()/wait()的那个线程上,是同步执行。在 UI 线程get()一个 deferred 任务,界面会直接卡死。std::async返回的future一旦析构,会阻塞等待任务完成(等价于隐式join)。它不会「后台跑着不管」,也不是 detach。
6.3 第一招:用 wait_for(0) 判断任务到底有没有真跑起来
这是工程师必备的一个小技巧:
auto f = std::async([] { return work(); });
// 返回 deferred,说明被延迟了:连线程都没起,任务根本没在跑
if (f.wait_for(std::chrono::seconds(0)) == std::future_status::deferred) {
// 在这里决定:是就地同步执行,还是报错,还是换一种方式
}wait_for 传入 0 时长是合法的。只有真正在跑的任务才可能返回 timeout,被延迟的任务会立刻返回 deferred。
6.4 第二招:别让 future 在循环里「就地析构」
这是全网出现频率最高的 std::async 事故,没有之一:
// ❌ 反面教材:看着像「发出去就不管」,其实一轮都没并行
for (int i = 0; i < 3; ++i) {
std::async(std::launch::async, [] { sleep_for_1s(); });
} // future 就地析构 -> 阻塞等待 -> 总耗时约 3 秒
// ✅ 正确姿势:把取货单先收起来,最后统一取
std::vector<std::future<void>> fs;
for (int i = 0; i < 3; ++i) {
fs.push_back(std::async(std::launch::async, [] { sleep_for_1s(); }));
}
for (auto& f : fs) f.get(); // 总耗时约 1 秒记住这句话:std::async 返回的 future,是任务生命周期的「缰绳」。缰绳一松手(析构),程序就会站在原地等它跑完。 想真正并行,就把所有缰绳攥在容器里。
6.5 并行 map:最实用的一个模式
template <class F>
auto parallel_map(const std::vector<int>& input, F f) {
std::vector<std::future<decltype(f(input[0]))>> futures;
futures.reserve(input.size());
for (int x : input) {
futures.push_back(
std::async(std::launch::async, [x, f] { return f(x); })
);
}
std::vector<decltype(f(input[0]))> out;
out.reserve(futures.size());
for (auto& fut : futures) {
out.push_back(fut.get()); // 逐个取货,异常也会在这里冒出来
}
return out;
}注意:这是无限制并发版本,input 有一万个元素就会开一万个线程。生产代码请配合线程池 + 信号量限流。
七、std::future_status:三个取值,说清三种处境
wait_for() 和 wait_until() 不返回结果,而是返回一个 std::future_status。它是唯一能让你「不等死」的工具。
| 取值 | 含义 | 你该做什么 |
|---|---|---|
ready | 共享状态已就绪 | 立刻 get(),不会阻塞(也可能立刻抛异常) |
timeout | 还没好,时间到了 | 任务可能仍在别的线程跑着,选择继续等或放弃 |
deferred | 任务被延迟了,根本没在跑 | 这不是超时,需要显式 get()/wait() 触发执行 |
⚠️ 重点区分:timeout 是「在跑但没跑完」,deferred 是「压根没启动」。这两个语义完全不同,很多 bug 就是把它们混为一谈。
7.1 轮询等待(不阻塞 UI / 主循环)
auto f = std::async(std::launch::async, [] { return slow_work(); });
while (f.wait_for(std::chrono::milliseconds(100)) != std::future_status::ready) {
// 没就绪就先干别的:刷进度、处理别的队列……
}
int value = f.get(); // 此时已就绪,立刻返回7.2 带超时的「放弃式」等待
auto f = std::async(std::launch::async, [] { return slow_work(); });
if (f.wait_for(std::chrono::seconds(2)) == std::future_status::ready) {
auto v = f.get();
} else {
// 超时:这个任务还在跑
// 注意:std::future 没有 cancel 接口,只能丢弃结果(任务会继续跑到结束)
}
⚠️ 这里有个残酷事实:标准库的 future 没有取消机制。想取消,只能自己再传一个 std::atomic<bool> 或者 std::stop_token(C++20)。
八、横向对比:一张表完成选型
| 组件 | 可拷贝 | get() 次数 | 谁写结果 | 典型场景 |
|---|---|---|---|---|
std::future | 否,只可 move | 1 次 | 生产者方 | 线程结果、async 返回值 |
std::shared_future | 是 | 任意次 | 生产者方 | 一份结果多方消费、重复读取 |
std::promise | 否,只可 move | 不适用(生产端) | 自己 set_value/set_exception | 回调里回传数据、跨层传值 |
std::packaged_task | 否,只可 move | 不适用(生产端) | 执行时自动写入 | 线程池、任务队列 |
std::async | 否(返回 future) | 1 次 | 由实现代为写入 | 一行代码启动并发任务 |
选型口诀:
- 只是想起个任务拿结果 →
std::async(务必显式写launch::async) - 拿到结果后要给多个消费者 →
future.share()变shared_future - 结果来自别人的回调 / 事件(不是你能控制的可调用对象)→
std::promise - 任务要排队、复用、进线程池 →
std::packaged_task - 要判断状态 / 带超时 → 配合
std::future_status
九、组合关系:其实只有一条主线
把这 7 个名字连起来看,底下只有一条主线:
┌──────────────────────────────┐
std::promise ────►│ │
std::packaged_task ─┼──► [ 共享状态:值/异常/未就绪 ] ──► std::future ──► std::shared_future
std::async ────►│ │
└──────────────────────────────┘
▲
std::launch ────┘(决定怎么启动)
std::future_status(决定怎么等)std::promise←→std::future:最原始的一对,手动写、手动取。std::packaged_task:内部自带一个promise,执行完替你set_value/set_exception。std::async:内部相当于std::thread+std::packaged_task+future,再包上启动策略。std::shared_future:把future那端的「独占」换成「共享」,生产端完全不用改。std::launch决定async怎么起线程;std::future_status决定你怎么等。
真正的知识内核只有一个:共享状态(shared state) + 一次性写入 + 一次性(或多次)读取。 把这句话吃透,7 个组件的 API 都只是它的不同外衣。
十、踩坑清单:这 8 条建议背下来
- 对同一个
future调用两次get():未定义行为,libstdc++ 抛std::future_error(no_state)。取之前先用valid()判断。 promise忘了set_value就析构:关联的future会收到broken_promise错误,而不是永远阻塞 —— 但仍然是个逻辑 bug。std::async不指定策略:任务可能被延迟到get()时才同步执行,导致「假并发」,线程数也和预期不符。std::async的future就地析构:会阻塞等待,循环里这么写等于串行。正确做法是收集到容器里统一get()。- 误以为
wait()会抛异常:wait()只等不取,异常只在get()时才被重新抛出。 deferred任务从不get()/wait():它永远不会执行。注意区别 —— 只有async任务才会在future析构时被等待。- 想拷贝
future分发给多个线程:编译不过。先share()成shared_future再分发。 - 把
get()放在持锁区里:get()是阻塞的,持锁阻塞等于把所有等待者一起拖死。取结果之前先把锁放掉。
十一、回到开头:用这套设施重写那段代码
现在再回头看引子里那段 mutex + condition_variable 的样板代码。用 promise/future 重写,是这样的:
#include <future>
#include <iostream>
#include <thread>
int main() {
std::promise<int> prom;
std::future<int> fut = prom.get_future();
std::thread t([&prom] { prom.set_value(42); });
std::cout << fut.get() << std::endl; // 42
t.join();
return 0;
}对比一下:
| 维度 | mutex + 条件变量 | promise + future |
|---|---|---|
| 代码行数 | 约 15 行 | 约 6 行 |
| 共享变量 | 有(result、ready) | 无(业务变量零共享) |
| 异常传播 | ❌ 做不到,直接 terminate | ✅ set_exception + get() |
| 时序正确性 | 靠人脑推理 | 由库保证 |
| 超时等待 | 需要另写 wait_for 循环 | wait_for 直接返回 future_status |
这就是 <future> 存在的意义:它把「跨线程传结果」这件事,从一个需要小心推理的并发问题,降维成了一个简单的「发货 / 取货」流程问题。
十二、延伸:C++20 之后怎么办
标准库的这套设施已经相当完整,但有几个历史遗留缺口,值得知道:
- 没有取消机制。
std::future无法 cancel。C++20 引入了std::stop_token/std::stop_source,配合std::jthread可以做协作式取消,但它不是future的一部分,需要你自己把 token 传进任务里。 - 没有
then/ 回调链。标准库至今(C++23)没有提供future.then()。想要链式异步,要么自己包一层,要么用第三方库(folly::Future、cppcoro)。 - 协程不能直接
co_await std::future。C++20 协程需要 awaiter,标准future没有实现。想用需要自己写适配器。 std::async的线程管理是「黑盒」。你无法控制它用多少线程、是否复用。要精细控制,请自己写线程池(见 5.3)或直接用std::thread+packaged_task。
一句话总结现状:std::future 家族适合「一次性、拿到结果就完事」的场景;需要复杂的异步编排,就得上协程或第三方 Future 库。
十三、总结
最后把整篇串成一句话收尾:
std::future是一张取货单,std::shared_future是它的复印件,std::promise是手动发货,std::packaged_task是自动发货机,std::async是下单即发货的快递柜,std::launch决定发货时机,std::future_status告诉你货到了没有。七个名字,一个心智模型,剩下的全是细节。
如果你只能记住三件事,请记住这三件:
future只能get()一次,要多次取就先share()。std::async一定要显式写std::launch::async,并且别让返回的future就地析构。promise是唯一能「从别人的回调里把结果塞回来」的工具,也是唯一能优雅传异常的工具。
参考与延伸阅读
- cppreference:std::future
- cppreference:std::promise
- cppreference:std::packaged_task
- cppreference:std::async
- 《C++ Concurrency in Action》(2nd Edition),Anthony Williams,第 4 章
如果这篇对你有帮助,欢迎点赞、收藏、关注。评论区聊聊:你在项目里被 std::async 的默认策略坑过吗?
到此这篇关于C++ 异步编程全景:一次讲透 std::future详解的文章就介绍到这了,更多相关C++ 异步编程std::future详解内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!


最新评论