好,准备好进入 std::future 和 std::promise 的奇妙世界了吗? 让我们开始这段异步任务之旅!
C++ std::future 与 std::promise:异步任务结果的传递与获取
大家好!欢迎来到今天的“异步魔法师训练营”。今天我们要学习的是C++中实现异步编程的两个重要角色:std::future 和 std::promise。 它们就像异步世界里的信使,负责传递任务的结果,让你可以在主线程中安全地接收,而不会被漫长的计算卡住。
异步编程的必要性:告别卡顿
想象一下,你正在开发一个图像处理程序。用户点击“锐化”按钮后,如果主线程直接进行复杂的图像计算,那么界面就会卡死,用户只能眼巴巴地等着,体验非常糟糕。
异步编程就是解决这个问题的利器。我们可以把耗时的图像处理任务放到另一个线程中执行,主线程继续响应用户的操作,等计算完成后再通知主线程更新图像。
std::future 和 std::promise 就是实现这种机制的关键。
std::promise:承诺交付结果的生产者
std::promise,顾名思义,是一个“承诺”。 它承诺在未来的某个时刻,会提供一个特定类型的值。 我们可以把它看作是一个“结果容器”,用于存放异步任务的计算结果。
-
创建
std::promise对象:#include <iostream> #include <future> #include <thread> int main() { std::promise<int> myPromise; // 创建一个承诺,类型为 int return 0; }这行代码创建了一个
std::promise对象,它承诺将来会提供一个int类型的值。 -
获取与
std::promise关联的std::future:std::future<int> myFuture = myPromise.get_future(); // 获取与 promise 关联的 futuremyPromise.get_future()返回一个std::future对象,它代表了对未来结果的期望。这个future对象会“监听”对应的promise对象,一旦promise存储了结果,future就可以获取到这个结果。 注意get_future()只能调用一次。 -
在异步任务中设置
std::promise的值:std::thread t([&](std::promise<int> prom) { try { // 模拟耗时计算 std::this_thread::sleep_for(std::chrono::seconds(2)); int result = 42; prom.set_value(result); // 设置承诺的值 } catch (...) { prom.set_exception(std::current_exception()); // 设置异常 } }, std::move(myPromise)); t.detach();这段代码创建了一个新的线程,在这个线程中,我们模拟了一个耗时的计算过程,并将结果
42通过prom.set_value(result)设置到promise对象中。 注意,这里使用了std::move,因为promise只能与一个线程关联,不能被复制。
set_exception用于设置异常,如果异步任务执行过程中发生了异常,我们可以将异常信息设置到promise中,这样future就可以捕获到这个异常。set_value_at_thread_exit()用于在线程退出时设置值。这可以确保即使线程在设置值之前退出,future仍然可以获得结果。
std::future:等待并获取结果的接收者
std::future,顾名思义,代表了“未来”的结果。 它是一个只读的对象,用于从异步任务中获取结果。 我们可以把它看作是一个“结果接收器”。
-
等待
std::future的结果:int value = myFuture.get(); // 等待并获取 future 的结果 std::cout << "Result: " << value << std::endl;myFuture.get()会阻塞当前线程,直到future准备好(即对应的promise已经设置了值或者异常)。 一旦future准备好,get()方法就会返回结果或者抛出异常。 注意get()只能调用一次。可以使用
wait()或wait_for()函数来等待future的结果,而不会阻塞。// 等待 future 完成,但不获取结果 myFuture.wait(); // 等待 future 完成,最多等待 1 秒 std::future_status status = myFuture.wait_for(std::chrono::seconds(1)); if (status == std::future_status::ready) { // future 已准备好 int value = myFuture.get(); std::cout << "Result: " << value << std::endl; } else if (status == std::future_status::timeout) { // 超时 std::cout << "Timeout!" << std::endl; } else { // Deferred std::cout << "Deferred!" << std::endl; }std::future_status枚举定义了wait_for()函数的返回值:std::future_status::ready:future已准备好,可以获取结果。std::future_status::timeout:等待超时。std::future_status::deferred:future与延迟函数关联(稍后会讲到)。
-
获取
std::future的状态:if (myFuture.valid()) { // future 有效 // ... }myFuture.valid()用于检查future对象是否有效。 如果future对象是通过promise.get_future()创建的,或者通过std::async创建的,那么它是有效的。
完整示例:std::promise 和 std::future 的协同工作
#include <iostream>
#include <future>
#include <thread>
int main() {
std::promise<int> myPromise;
std::future<int> myFuture = myPromise.get_future();
std::thread t([&](std::promise<int> prom) {
try {
std::this_thread::sleep_for(std::chrono::seconds(2));
int result = 42;
prom.set_value(result);
} catch (...) {
prom.set_exception(std::current_exception());
}
}, std::move(myPromise));
t.detach();
try {
int value = myFuture.get();
std::cout << "Result: " << value << std::endl;
} catch (const std::exception& e) {
std::cerr << "Exception: " << e.what() << std::endl;
}
return 0;
}
在这个例子中,promise 在另一个线程中设置了值,而 future 在主线程中等待并获取这个值。 这就是 std::promise 和 std::future 的基本工作方式。
std::async:更便捷的异步任务启动方式
std::async 是一个启动异步任务的便捷函数。 它可以自动创建 std::promise 和 std::future,并将任务放到另一个线程中执行(或者延迟执行,稍后会讲到)。
-
使用
std::async启动异步任务:#include <iostream> #include <future> int calculateSum(int a, int b) { std::this_thread::sleep_for(std::chrono::seconds(1)); // 模拟耗时计算 return a + b; } int main() { std::future<int> sumFuture = std::async(std::launch::async, calculateSum, 10, 20); std::cout << "Doing other things..." << std::endl; int sum = sumFuture.get(); std::cout << "Sum: " << sum << std::endl; return 0; }std::async(std::launch::async, calculateSum, 10, 20)会创建一个新的线程来执行calculateSum(10, 20)函数,并返回一个std::future<int>对象。 我们可以通过future.get()来获取计算结果。 -
std::launch策略:std::async函数的第一个参数是std::launch策略,它控制任务的执行方式:std::launch::async:强制在新的线程中执行任务。std::launch::deferred:延迟执行任务,直到调用future.get()或future.wait()。std::launch::any:由系统决定是立即执行还是延迟执行(默认策略)。
std::launch::deferred的妙用:std::future<int> deferredFuture = std::async(std::launch::deferred, calculateSum, 10, 20); std::cout << "Deferred task created, but not executed yet." << std::endl; // 在这里可以做其他事情 std::cout << "Now, let's get the result..." << std::endl; int sum = deferredFuture.get(); // 此时才会执行 calculateSum 函数 std::cout << "Sum: " << sum << std::endl;使用
std::launch::deferred策略时,calculateSum函数不会立即执行。 只有当我们调用deferredFuture.get()时,它才会在当前线程中执行。 这在某些情况下可以提高性能,避免不必要的线程创建。
异常处理:异步世界里的意外情况
异步任务中也可能发生异常。 我们需要确保这些异常能够被正确地捕获和处理。
-
在
std::promise中设置异常:std::thread t([&](std::promise<int> prom) { try { // 模拟可能抛出异常的计算 if (rand() % 2 == 0) { throw std::runtime_error("Something went wrong!"); } int result = 42; prom.set_value(result); } catch (...) { prom.set_exception(std::current_exception()); // 设置异常 } }, std::move(myPromise));如果异步任务中发生了异常,我们可以使用
prom.set_exception(std::current_exception())将异常信息设置到promise中。std::current_exception()用于捕获当前线程中的异常。 -
在
std::future中捕获异常:try { int value = myFuture.get(); std::cout << "Result: " << value << std::endl; } catch (const std::exception& e) { std::cerr << "Exception: " << e.what() << std::endl; }当
future.get()被调用时,如果promise中存储了异常,那么get()方法会抛出这个异常。 我们可以使用try-catch块来捕获并处理这个异常。
std::shared_future:共享的未来
有时候,我们希望多个线程共享同一个 future 对象,并获取相同的结果。 这时,可以使用 std::shared_future。
-
创建
std::shared_future:std::promise<int> myPromise; std::shared_future<int> mySharedFuture = myPromise.get_future().share(); // 创建 shared_futuremyPromise.get_future().share()会返回一个std::shared_future对象,它可以被多个线程共享。 注意,你需要先从promise获取一个普通的future,然后再调用share()方法。 -
多个线程共享
std::shared_future:#include <iostream> #include <future> #include <thread> #include <vector> int main() { std::promise<int> myPromise; std::shared_future<int> mySharedFuture = myPromise.get_future().share(); std::vector<std::thread> threads; for (int i = 0; i < 3; ++i) { threads.emplace_back([&](int threadId) { try { int value = mySharedFuture.get(); // 所有线程都获取相同的结果 std::cout << "Thread " << threadId << " got result: " << value << std::endl; } catch (const std::exception& e) { std::cerr << "Thread " << threadId << " Exception: " << e.what() << std::endl; } }, i); } std::this_thread::sleep_for(std::chrono::seconds(1)); // 确保线程启动 myPromise.set_value(42); // 设置 promise 的值 for (auto& t : threads) { t.join(); } return 0; }在这个例子中,三个线程共享同一个
shared_future对象,并获取相同的结果42。
总结:std::future 和 std::promise 的角色
为了更好地理解 std::future 和 std::promise 的角色,我们可以用一个表格来总结:
| 特性 | std::promise |
std::future |
|---|---|---|
| 角色 | 结果的生产者,负责设置结果或异常 | 结果的消费者,负责等待并获取结果或捕获异常 |
| 写权限 | 可写(只能设置一次值或异常) | 只读 |
| 所有权 | 独占 | 独占(std::shared_future 可以共享) |
| 关联 | 与一个 std::future 对象关联 |
与一个 std::promise 对象关联 |
| 主要方法 | set_value(), set_exception(), get_future() |
get(), wait(), wait_for(), valid(), share() |
| 使用场景 | 在异步任务中设置结果 | 在主线程中获取异步任务的结果 |
高级应用:std::packaged_task
std::packaged_task 是一个将函数或可调用对象“打包”成一个异步任务的工具。 它可以与 std::future 结合使用,方便地管理异步任务。
-
使用
std::packaged_task:#include <iostream> #include <future> #include <thread> int calculateSquare(int x) { std::this_thread::sleep_for(std::chrono::seconds(1)); return x * x; } int main() { std::packaged_task<int(int)> task(calculateSquare); // 打包函数 std::future<int> future = task.get_future(); // 获取 future std::thread t(std::move(task), 5); // 在线程中执行任务 int square = future.get(); // 获取结果 std::cout << "Square: " << square << std::endl; t.join(); return 0; }std::packaged_task<int(int)> task(calculateSquare)创建一个packaged_task对象,它将calculateSquare函数打包成一个异步任务。task.get_future()返回与该任务关联的future对象。 我们可以将task对象移动到另一个线程中执行,并通过future.get()获取结果。
总结
std::future 和 std::promise 是 C++ 中实现异步编程的重要工具。 它们可以帮助我们避免主线程阻塞,提高程序的响应速度和用户体验。 掌握它们的使用方法,可以让你在异步编程的世界里游刃有余。 std::async 和 std::packaged_task 则提供了更便捷的异步任务启动和管理方式。 希望今天的课程能帮助你成为一名真正的“异步魔法师”!