好的,没问题!让我们开始这场关于C++ std::shared_future
的技术讲座吧!
讲座主题:C++ std::shared_future
:多个 Future 共享一个异步结果
大家好!欢迎来到今天的C++技术讲座。今天我们要聊的是一个非常有趣且实用的工具:std::shared_future
。
想象一下,你是一家快餐店的老板,顾客络绎不绝。每个顾客都想点一份美味的汉堡,而制作汉堡需要一段时间。如果你只有一个厨师(线程),那么每个顾客都必须排队等待,效率非常低。
为了提高效率,你决定雇佣多个服务员(线程),让他们同时为不同的顾客服务。但是,只有一份汉堡制作的配方(异步任务的结果),所有服务员都需要使用这份配方才能制作出正确的汉堡。
std::shared_future
就好比这份汉堡配方,它可以被多个服务员(线程)共享,让他们都能获取到汉堡的制作方法(异步任务的结果)。
什么是 std::future
?
在深入了解 std::shared_future
之前,我们先简单回顾一下 std::future
。std::future
是C++中用于获取异步操作结果的机制。它代表着一个异步任务的返回值,但这个返回值可能在将来某个时刻才能得到。
#include <iostream>
#include <future>
#include <thread>
int calculate_sum(int a, int b) {
std::cout << "Calculating sum in thread: " << std::this_thread::get_id() << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(2)); // 模拟耗时操作
return a + b;
}
int main() {
std::future<int> result = std::async(std::launch::async, calculate_sum, 10, 20);
std::cout << "Waiting for result..." << std::endl;
int sum = result.get(); // 获取异步操作的结果,如果结果还没准备好,会阻塞直到结果可用
std::cout << "Sum: " << sum << std::endl;
return 0;
}
在这个例子中,std::async
启动了一个异步任务 calculate_sum
,它会在另一个线程中计算两个数的和。result
是一个 std::future<int>
对象,它代表着这个异步任务的返回值。result.get()
会阻塞当前线程,直到异步任务完成并返回结果。
std::future
的局限性
std::future
有一个重要的限制:它只能被 get()
一次。也就是说,一旦你通过 future.get()
获取了异步任务的结果,这个 future
对象就失效了,再次调用 get()
会抛出异常。
#include <iostream>
#include <future>
#include <thread>
int main() {
std::future<int> result = std::async(std::launch::async, []() { return 42; });
int value1 = result.get();
std::cout << "Value 1: " << value1 << std::endl;
try {
int value2 = result.get(); // 再次调用 get() 会抛出异常
std::cout << "Value 2: " << value2 << std::endl;
} catch (const std::future_error& e) {
std::cerr << "Exception: " << e.what() << std::endl; // 输出 "std::future_error: future_already_retrieved"
}
return 0;
}
这个限制在某些情况下会带来不便。例如,如果多个线程都需要访问同一个异步任务的结果,那么使用 std::future
就无法满足需求。
std::shared_future
的登场
std::shared_future
就是为了解决 std::future
的这个局限性而诞生的。它允许多个线程共享同一个异步任务的结果。
std::shared_future
可以从 std::future
对象移动构造而来,或者通过 future.share()
方法创建。一旦创建了 std::shared_future
,多个线程就可以通过 shared_future.get()
方法安全地访问异步任务的结果,而不会使 shared_future
对象失效。
#include <iostream>
#include <future>
#include <thread>
#include <vector>
int main() {
std::future<int> result = std::async(std::launch::async, []() {
std::cout << "Calculating result in thread: " << std::this_thread::get_id() << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(2)); // 模拟耗时操作
return 42;
});
std::shared_future<int> shared_result = result.share(); // 从 future 创建 shared_future
std::vector<std::thread> threads;
for (int i = 0; i < 3; ++i) {
threads.emplace_back([shared_result, i]() {
std::cout << "Thread " << i << " waiting for result in thread: " << std::this_thread::get_id() << std::endl;
int value = shared_result.get();
std::cout << "Thread " << i << " got result: " << value << " in thread: " << std::this_thread::get_id() << std::endl;
});
}
for (auto& thread : threads) {
thread.join();
}
return 0;
}
在这个例子中,我们首先创建了一个 std::future<int>
对象 result
,然后通过 result.share()
方法创建了一个 std::shared_future<int>
对象 shared_result
。接下来,我们创建了 3 个线程,每个线程都通过 shared_result.get()
方法获取异步任务的结果。所有线程都可以安全地访问结果,而不会发生任何异常。
std::shared_future
的常用方法
方法 | 描述 |
---|---|
get() |
获取异步任务的结果。如果结果还没准备好,会阻塞直到结果可用。多个线程可以同时调用 get() ,而不会使 shared_future 对象失效。 |
valid() |
检查 shared_future 对象是否有效,即是否关联到一个异步任务。 |
wait() |
阻塞当前线程,直到异步任务完成。 |
wait_for() |
阻塞当前线程,直到异步任务完成或者超时。 |
wait_until() |
阻塞当前线程,直到异步任务完成或者到达指定的时间点。 |
std::shared_future
的应用场景
std::shared_future
在以下场景中非常有用:
- 多个线程需要访问同一个异步任务的结果: 这是
std::shared_future
最典型的应用场景。例如,多个线程需要从数据库中读取相同的数据,或者多个线程需要处理同一个图像。 - 需要缓存异步任务的结果: 可以使用
std::shared_future
将异步任务的结果缓存起来,以便后续的线程可以直接访问缓存的结果,而无需重新执行异步任务。 - 实现发布/订阅模式: 可以使用
std::shared_future
作为发布者,将异步任务的结果发布给多个订阅者(线程)。
代码示例:使用 std::shared_future
实现缓存
#include <iostream>
#include <future>
#include <thread>
#include <chrono>
class DataCache {
public:
DataCache(std::function<int()> data_provider) : data_provider_(data_provider) {}
std::shared_future<int> get_data() {
std::lock_guard<std::mutex> lock(mutex_);
if (!cached_data_.valid()) {
cached_data_ = std::async(std::launch::async, data_provider_).share();
}
return cached_data_;
}
private:
std::function<int()> data_provider_;
std::shared_future<int> cached_data_;
std::mutex mutex_;
};
int main() {
// 模拟一个耗时的数据获取操作
auto data_provider = []() {
std::cout << "Fetching data..." << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(3));
return 123;
};
DataCache cache(data_provider);
std::vector<std::thread> threads;
for (int i = 0; i < 5; ++i) {
threads.emplace_back([&cache, i]() {
std::cout << "Thread " << i << " requesting data..." << std::endl;
std::shared_future<int> data = cache.get_data();
int value = data.get();
std::cout << "Thread " << i << " got data: " << value << std::endl;
});
}
for (auto& thread : threads) {
thread.join();
}
return 0;
}
在这个例子中,DataCache
类使用 std::shared_future
来缓存数据。第一次调用 get_data()
时,会启动一个异步任务来获取数据,并将结果存储在 cached_data_
中。后续的调用 get_data()
时,会直接返回 cached_data_
,而不会重新执行异步任务。这样就实现了数据的缓存,提高了程序的效率。
std::shared_future
的注意事项
- 异常处理: 如果异步任务抛出异常,那么所有调用
shared_future.get()
的线程都会收到相同的异常。因此,在使用std::shared_future
时,需要注意异常处理。 - 避免死锁: 在使用
std::shared_future
时,需要注意避免死锁。例如,如果一个线程在等待shared_future
的结果,而另一个线程又在持有锁的情况下尝试访问shared_future
,就可能导致死锁。 - 生命周期管理:
std::shared_future
对象必须在所有使用它的线程都结束后才能被销毁。否则,可能会导致程序崩溃。 通常,使用基于RAII(Resource Acquisition Is Initialization)的智能指针可以自动管理对象生命周期。
总结
std::shared_future
是 C++ 中一个非常有用的工具,它允许多个线程共享同一个异步任务的结果。通过使用 std::shared_future
,可以提高程序的效率,并简化多线程编程。
std::future
vs std::shared_future
为了更好地理解std::shared_future
,我们用一个表格来总结一下它和std::future
的区别:
特性 | std::future |
std::shared_future |
---|---|---|
可访问性 | 只能被 get() 一次 |
可以被多个线程多次 get() |
所有权 | 拥有独占的异步结果所有权 | 共享异步结果的所有权 |
使用场景 | 单个线程需要获取异步结果 | 多个线程需要共享同一个异步结果 |
创建方式 | 通过 std::async , std::promise 等创建 |
从 std::future 对象移动构造或使用 future.share() |
高级话题:std::promise
和 std::packaged_task
虽然今天的主题是 std::shared_future
,但为了更全面地理解它的使用场景,我们还需要简单了解一下 std::promise
和 std::packaged_task
。
-
std::promise
:std::promise
允许你手动设置std::future
的结果。这在某些情况下非常有用,例如,当你需要从外部事件(例如,网络连接)中获取异步任务的结果时。#include <iostream> #include <future> #include <thread> int main() { std::promise<int> promise; std::future<int> future = promise.get_future(); std::shared_future<int> shared_future = future.share(); std::thread t([&promise]() { std::cout << "Performing some work..." << std::endl; std::this_thread::sleep_for(std::chrono::seconds(2)); promise.set_value(100); // 设置 future 的结果 }); std::cout << "Waiting for result..." << std::endl; int result = shared_future.get(); std::cout << "Result: " << result << std::endl; t.join(); return 0; }
-
std::packaged_task
:std::packaged_task
允许你将一个函数或可调用对象包装成一个异步任务。它会自动创建一个std::future
对象,并将其与异步任务关联起来。#include <iostream> #include <future> #include <thread> int main() { std::packaged_task<int(int, int)> task([](int a, int b) { std::cout << "Calculating sum..." << std::endl; std::this_thread::sleep_for(std::chrono::seconds(2)); return a + b; }); std::future<int> future = task.get_future(); std::shared_future<int> shared_future = future.share(); std::thread t(std::move(task), 10, 20); // 启动异步任务 std::cout << "Waiting for result..." << std::endl; int result = shared_future.get(); std::cout << "Result: " << result << std::endl; t.join(); return 0; }
std::shared_future
与 std::shared_ptr
的关系
虽然名字相似,但 std::shared_future
和 std::shared_ptr
是完全不同的概念。std::shared_ptr
是一个智能指针,用于管理动态分配的内存,而 std::shared_future
用于共享异步任务的结果。
不过,它们之间也有一些联系。例如,你可以使用 std::shared_ptr
来管理 std::shared_future
对象,以确保在所有使用它的线程都结束后才释放 std::shared_future
对象。
总结的总结
今天我们深入探讨了 C++ 中的 std::shared_future
,了解了它的用途、常用方法、应用场景以及与其他相关概念的关系。希望通过今天的讲座,大家能够更好地理解和使用 std::shared_future
,并在实际开发中发挥它的优势。
记住,std::shared_future
就像一份可以被无数人复制的美味汉堡配方,它让你的多线程程序可以高效地共享异步任务的结果,让你的代码更加优雅和强大!
感谢大家的参与!希望下次还能和大家一起探讨更多有趣的 C++ 技术。