C++ 中 future / promise / async 实现多线程详解(含可运行示例)
下面把上一段被截断的内容补完,并给出一个完整可编译运行的例子。
一、心智模型:共享状态(Shared State)
text
promise —[set_value/set_exception]—> 共享状态 <—[get/wait]— future
std::async 则是"自动挡":创建线程 + 建 promise + 跑函数 + 写结果,打包返回 future
future 和 promise 本身不存值,只是"共享状态"的两端句柄。共享状态里放着:
一个原子状态标志(未就绪 / 就绪 / 已取走);
值存储区或异常指针;
完成通知机制(条件变量/futex)。
由此得到几条关键语义:
-
get() 只能调一次,取走后 valid() 变 false;
-
get() 阻塞到就绪,然后返回值或重新抛出存入的异常;
-
wait() 只等不取,之后仍可 get();
-
promise 析构时若未设值,自动写入 broken_promise 异常,避免等待方永久卡死。
二、std::promise:手工写端
cpp
std::promise<int> p;
std::future<int> f = p.get_future(); // 只能取一次,第二次抛 future_already_retrieved
std::thread t([p = std::move(p)]() mutable {
p.set_value(42); // 只能成功一次,否则 promise_already_satisfied
});
int v = f.get(); // 阻塞直到 42 就绪
t.join();
要点:
-
promise 只能移动,传给线程要 std::move;
-
set_value / set_exception 只能调一次;
-
set_value_at_thread_exit:结果推迟到本线程 TLS 析构完毕后才可见;
-
异常传递是这套机制的核心价值:
cpp
try { throw std::runtime_error("boom"); }
catch (…) { p.set_exception(std::current_exception()); }
// 另一端 f.get() 会重新抛出 runtime_error
三、std::future:读端
| get() | 等待+取值(或抛异常),之后失效 |
| wait() | 只等就绪 |
| wait_for(d) / wait_until(tp) | 超时等待,返回 ready / timeout / deferred |
| share() | 转成 shared_future<T> |
future 是独占的。多个线程都要读时用 std::shared_future<T>(可拷贝、可多次 get()):
cpp
std::shared_future<int> sf = p.get_future().share();
std::thread a([sf]{ std::cout << sf.get(); });
std::thread b([sf]{ std::cout << sf.get(); });
p.set_value(99);
四、std::async:任务级并发
4.1 三种启动策略
cpp
std::async(std::launch::async, f, args…); // 强制新线程,立即开始
std::async(std::launch::deferred, f, args…); // 不建线程,get/wait 时在调用线程同步执行
std::async(f, args…); // 默认 = async | deferred,由实现选
deferred 的行为:任务体被"存起来",直到对 future 调 get() / wait() / wait_for() 才在当前调用线程上同步执行;此时 wait_for 立刻返回 future_status::deferred(表示"还没开始"),而不是 timeout。
4.2 ⚠️ 最著名的两个陷阱
陷阱 1:不保存返回值 → 退化成同步
cpp
std::async(std::launch::async, []{ do_work(); }); // 临时 future 在本语句末尾析构
如果 async 返回的 future 是临时对象被立即析构,而它关联的任务尚未完成,标准规定析构会阻塞等待任务结束(相当于隐式 join)。结果这句"异步"实际上是同步的。务必把 future 存进变量。
陷阱 2:默认策略不确定
std::async(f, …) 默认可能被实现选成 deferred,从而根本不并发。要保证并发,显式写 std::launch::async。
// g++ -std=c++17 -pthread main.cpp -o demo
#include <future>
#include <thread>
#include <iostream>
#include <vector>
#include <chrono>
#include <stdexcept>
#include <numeric>
#include <string>
using namespace std::chrono_literals;
// 模拟耗时计算
int heavy_square(int x) {
std::this_thread::sleep_for(300ms);
return x * x;
}
int main() {
// ———- 1. std::async 并行计算 ———-
{
std::vector<std::future<int>> futures;
auto t0 = std::chrono::steady_clock::now();
for (int i = 1; i <= 4; ++i)
futures.push_back(std::async(std::launch::async, heavy_square, i));
int sum = 0;
for (auto& f : futures)
sum += f.get(); // 每个 get 阻塞到各自完成
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() – t0).count();
std::cout << "[1] sum = " << sum << " (1+4+9+16=30), 耗时 " << ms
<< " ms(串行应约 1200 ms)\\n";
}
// ———- 2. promise/future 手工通信 ———-
{
std::promise<std::string> p;
std::future<std::string> f = p.get_future();
std::thread t([p = std::move(p)]() mutable {
std::this_thread::sleep_for(200ms);
p.set_value("hello from worker thread");
});
std::cout << "[2] " << f.get() << "\\n"; // 阻塞直到被 set_value
t.join();
}
// ———- 3. 异常跨线程传递 ———-
{
auto f = std::async(std::launch::async, []() -> int {
throw std::runtime_error("boom inside async");
return 0; // 不可达
});
try {
(void)f.get(); // 重新抛出 runtime_error
} catch (const std::exception& e) {
std::cout << "[3] caught: " << e.what() << "\\n";
}
}
// ———- 4. wait_for 超时轮询 ———-
{
auto f = std::async(std::launch::async, [] {
std::this_thread::sleep_for(500ms);
return 7;
});
while (f.wait_for(100ms) != std::future_status::ready)
std::cout << "[4] still working…\\n";
std::cout << "[4] result = " << f.get() << "\\n";
}
// ———- 5. shared_future:一个结果多方消费 ———-
{
std::promise<int> p;
std::shared_future<int> sf = p.get_future().share();
std::thread a([sf]{ std::cout << "[5] A got " << sf.get() << "\\n"; });
std::thread b([sf]{ std::cout << "[5] B got " << sf.get() << "\\n"; });
p.set_value(99); // 广播唤醒 A、B
a.join(); b.join();
}
// ———- 6. broken_promise 演示 ———-
{
std::future<int> f;
{
std::promise<int> p;
f = p.get_future();
// p 未 set_value 就析构
}
try {
f.get();
} catch (const std::future_error& e) {
std::cout << "[6] future_error: " << e.code().message() << "\\n";
}
}
return 0;
}
