std::thread join前获取std::future值的实践与选型疑问
std::thread搭配std::future提前获取返回值的相关问题
学习std::thread与std::future相关特性时,编写了如下简易测试示例,目标是在std::thread执行结束前提前获取std::future的返回值:
#include <iostream> #include <future> #include <thread> #include <chrono> int foo(int a, int b) { return a + b; } int main() { auto task = std::packaged_task<int (int, int)>{foo}; auto future = task.get_future(); auto t = std::thread{ [&task](int a, int b) { using namespace std::chrono_literals; task(a, b); std::this_thread::sleep_for(5s); // 模拟耗时的后置操作 std::cout << "Thread ends.\n"; }, 1, 2 }; std::cout << future.get() << '\n'; std::cout << "Waiting for thread\n"; t.join(); return 0; }
疑问1:这类用法的真实业务落地场景是否包含日志记录、数据库存储等操作?
完全包含,这就是这类写法最常见的落地场景之一。
你这段代码的核心模式本质是:核心计算/业务逻辑执行完成后,立刻把结果通过future返回给等待的调用方,不需要阻塞调用方等待后续非核心收尾操作执行完成。
日志异步落盘、非强一致要求的数据库写入(比如操作流水、统计类数据上报)、消息投递、用户行为埋点、临时资源延迟清理这些操作,全是这个模式的典型适用场景——这些操作的共同特点是,不需要卡主流程等它执行完,晚个几秒执行也不影响核心业务的正确性,完全没必要让用户/调用方等这些操作跑完才拿到结果。
唯一要注意的是,这类用法一定要把控好后台工作线程的生命周期,别主流程都退出了后台收尾任务还没跑完导致数据丢失,示例里最后显式调用t.join()就是正确的做法,图省事直接detach放飞很容易出问题。
疑问2:如果移除代码中的std::this_thread::sleep_for(5s)语句,此时选用std::thread而非std::async是否仍具备实际意义?
就算移除5s的sleep模拟逻辑,选择std::thread而非std::async依然有实际意义,两者的设计定位本来就有差异,不存在谁完全替代谁的说法:
- 首先
std::async的默认启动策略是std::launch::async | std::launch::deferred,这个策略下C++标准不保证任务一定在独立的新线程执行,很可能直到你调用future.get()的时候,任务才在当前线程同步执行,根本达不到「先返回核心结果,后置逻辑后台跑」的效果。就算你显式指定std::launch::async强制异步执行,你也拿不到底层线程的原生句柄,没法做线程优先级调整、CPU核绑定、线程名设置这类线上排查问题常用的定制操作。 - 其次自己管理
std::thread的灵活度高很多:你可以把这个线程纳入你自己维护的线程池做复用,避免频繁创建销毁线程的开销;你可以自由控制线程回收时机、线程本地资源的清理逻辑、甚至给长时间运行的任务发中断信号,这些能力都是黑盒的std::async不提供的。 - 哪怕你去掉了sleep,只要任务执行完核心逻辑、把结果写入future之后,线程本身还要做和返回值无关的操作——比如上报任务执行耗时、清理线程本地缓存、把线程归还到池子里——那自己控制
std::thread的方案就比直接用std::async更适配需求。
当然如果你的需求非常简单,就是跑个独立任务拿返回值,没有任何线程定制、生命周期管控的需求,std::async写起来代码更短更省事,但这不代表手动用std::thread搭配packaged_task的写法没有实际价值。
内容的提问来源于stack exchange,提问作者MrGrees_
相关产品推荐
相关产品推荐

