好,准备好进入 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 关联的 future
myPromise.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_future
myPromise.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
则提供了更便捷的异步任务启动和管理方式。 希望今天的课程能帮助你成为一名真正的“异步魔法师”!