C++11线程库futures与promises
发布时间: 2024-12-10 02:55:33 阅读量: 1 订阅数: 12
C++线程库介绍
![C++11线程库futures与promises](https://inprogrammer.com/wp-admin/admin-ajax.php?action=rank_math_overlay_thumb&id=5261&type=play&hash=ac518a8d00bb7c7902e8d2fc2ce600c2)
# 1. C++11线程库概述与基础
## 简介
C++11 引入了一套全新的线程库,它提供了一种高层次的并发编程机制。这个库的引入,极大简化了多线程程序的设计和实现,使得开发者可以更容易地利用现代多核处理器的能力。
## 历史背景
在 C++11 之前,多线程编程往往依赖于平台特定的扩展或者第三方库。标准库的缺乏导致代码可移植性差,难以维护。为了解决这些问题,C++11 纳入了包括线程、互斥锁、条件变量、futures 和 promises 在内的多线程相关组件。
## 核心组件概览
C++11 线程库主要包含以下几个核心组件:
- `thread`:用于表示和控制线程。
- `mutex`、`lock`、`condition_variable`:用于线程同步和互斥。
- `async`、`future`、`promise`:用于在不同线程之间传递数据和异常。
## C++11 线程库的优势
C++11 线程库的设计哲学着重于易于理解和使用。它为多线程的创建、执行和管理提供了一套直观的抽象,支持基于任务的并发模式,并且能够与现代 C++ 编程风格(如 lambda 表达式)无缝集成。
C++11 线程库的推出,使得编写可移植、高效的多线程代码成为可能,是学习现代 C++ 并发编程不可或缺的一部分。接下来的章节中,我们将深入探讨 futures 和 promises,以及如何利用它们解决实际的并发编程问题。
# 2. 理解futures的使用和原理
## 2.1 futures简介
### 2.1.1 futures的概念和作用
在C++11线程库中,`futures`是并发编程中一个非常重要的概念,它提供了一种机制,允许异步任务的结果在将来某个时间点被检索。简单来说,`future`是一个对象,它异步地从另一个线程获取数据,而获取数据的操作可能还未完成或甚至还未开始。在C++中,`std::future`是实现这一功能的模板类。
`futures`的主要作用包括:
1. 提供异步操作的返回值访问。
2. 允许多个线程等待同一个异步操作的结果。
3. 隔离同步和异步代码部分,提高代码的可读性和维护性。
使用`future`可以将线程间的依赖降到最低,因为创建`future`对象的线程不需要等待其代表的异步操作完成,可以继续执行其他任务。而当需要结果时,只需在`future`对象上调用等待操作,即可暂停当前线程,直到异步操作完成并可安全访问结果。
### 2.1.2 创建和获取futures
创建一个`future`对象通常涉及启动一个异步操作,并将其与`future`对象相关联。在C++11中,可以使用`std::async`启动异步操作,也可以通过`std::packaged_task`或`std::promise`手动包装异步任务。
- 使用`std::async`启动异步任务:
```cpp
#include <future>
#include <iostream>
int main() {
// 启动一个异步任务计算结果
std::future<int> future_result = std::async(std::launch::async, []() { return 42; });
// 获取异步任务的结果
int result = future_result.get(); // 返回42
std::cout << "The answer is " << result << std::endl;
return 0;
}
```
- 使用`std::packaged_task`:
```cpp
#include <future>
#include <iostream>
int main() {
// 准备一个任务,比如计算某个函数的值
std::packaged_task<int()> task([]() { return 42; });
// 获取future对象
std::future<int> future_result = task.get_future();
// 执行任务
task();
// 获取任务结果
int result = future_result.get(); // 返回42
std::cout << "The answer is " << result << std::endl;
return 0;
}
```
在上述代码中,我们演示了如何使用`std::async`和`std::packaged_task`创建`future`对象并获取异步操作的结果。需要注意的是,在调用`future.get()`之前,如果对应的任务尚未完成,当前线程将会阻塞,直到获取到结果。另外,如果一个`future`对象在它所关联的异步操作完成前被销毁,则该操作会被中断,调用`get()`时会抛出异常。
## 2.2 futures的类型和特性
### 2.2.1 std::future与std::shared_future的区别
`std::future`和`std::shared_future`都是从异步操作中检索值的工具,但它们有细微的差别。
`std::future`:
- 通常用于单次异步结果的检索。
- 不能被复制,但可以被移动。
- 当`std::future`对象被销毁时,它所关联的异步操作会被取消,除非已经获取了它的值。
`std::shared_future`:
- 可以关联同一异步操作的多个结果。
- 允许多个线程多次获取值,且这些调用彼此之间不会互相影响。
- 可以被复制,适用于多个线程需要等待同一个异步操作的不同结果的场景。
例如,如果需要在多个线程中重复获取某个异步操作的结果,应当使用`std::shared_future`。
```cpp
#include <future>
#include <iostream>
int main() {
std::promise<int> prom;
std::shared_future<int> sfut(prom.get_future());
std::vector<std::thread> threads;
for(int i = 0; i < 5; ++i) {
threads.emplace_back([sfut]() {
int value = sfut.get(); // 在不同的线程中多次获取值
std::cout << "Value retrieved: " << value << std::endl;
});
}
prom.set_value(42); // 设置异步操作的结果
// 等待所有线程完成
for(auto& t : threads) {
t.join();
}
return 0;
}
```
### 2.2.2 futures中的异常处理
当异步操作产生异常时,异常信息会被存储在`future`对象中。使用`get()`方法时,如果异步操作失败,会抛出异常,并将异常信息传递给调用者。因此,正确地处理`get()`抛出的异常非常重要,尤其是在多线程环境中。
```cpp
#include <future>
#include <iostream>
#include <stdexcept>
int main() {
// 异步任务中产生异常
std::promise<int> prom;
auto future_result = prom.get_future();
std::thread t([&prom]() {
try {
// 异步任务执行中抛出异常
throw std::runtime_error("Error occurred");
} catch (...) {
// 将异常封装进promise中
prom.set_exception(std::current_exception());
}
});
// 获取异步任务的结果
try {
int result = future_result.get();
std::cout << "Result: " << result << std::endl;
} catch (const std::exception& e) {
// 异步操作中的异常被捕获
std::cout << "Caught exception: " << e.what() << std::endl;
}
t.join();
return 0;
}
```
在这个例子中,我们创建了一个异步任务,并在任务中故意抛出了一个异常。这个异常被`set_exception`方法捕获,并在后续使用`get()`方法时被抛出。正确地处理这种情况对于保证程序的健壮性至关重要。
## 2.3 futures的实践应用
### 2.3.1 异步计算示例
利用`std::async`和`std::future`可以很轻易地实现异步计算。下面是一个异步计算的例子:
```cpp
#include <future>
#include <iostream>
#include <chrono>
int compute(int n) {
std::this_thread::sleep_for(std::chrono::seconds(1));
return n * n;
}
int main() {
// 启动异步任务计算平方值
std::future<int> result = std::async(std::launch::async, compute, 99);
// 在等待结果的时候,执行其他任务
std::cout << "Doing something else meanwhile..." << std::endl;
// 获取异步任务的结果
int square = result.get();
std::cout << "The result is " << square << std::endl;
return 0;
}
```
在这个示例中,我们利用`std::async`启动了一个异步计算任务,并立即返回了一个`std::future`对象,表示这个异步任务的结果。主线程在等待结果的同时,执行了其他任务,而不是简单地空等。这种模式在实际应用中可以大幅提升程序的响应性和吞吐量。
### 2.3.2 使用futures进行并发控制
`futures`可以用于控制异步任务的执行顺序。通过确保在继续执行之前某个异步任务必须完成,我们可以实现并发控制。
```cpp
#include <future>
#include <iostream>
#include <thread>
void task1(std::promise<int>& prom) {
std::this_thread::sleep_for(std::chrono::seconds(2));
prom.set_value(1); // 任务1完成
}
void task2(std::future<int>& fut) {
int value = fut.get(); // 等待任务1完成
std::cout << "Task 2 started after Task 1 with value: " << value << std::endl;
}
int main() {
std::promise<int> prom;
std::future<int> fut = prom.get_future();
std::thread t1(task1, std::ref(prom));
std::thread t2(task2, std::ref(fut));
t1.join();
t2.join();
return 0;
}
```
在这个例子中,`task1`和`task2`是两个异步任务,其中`task2`需要等待`task1`完成后才能开始执行。这通过`promise`和`future`对象实现。`task1`完成之后通过`set_value`方法设置值,然后`task2`通过`get`方法检索该值以继续执行。这种模式可用于更复杂的并发任务,确保它们按照特定顺序执行。
在下一章中,我们将继续深入理解`promises`的原理,以及它们如何与`futures`紧密协作以实现复杂的并发控制。
# 3. promises的深入剖析
在并发编程中,promises是一个核心概念,允许一个线程提供一个值给另一个线程,而这个值可能在未来某个时刻才会被计算出来。在C++11中,std::promise及其对应的std::future提供了一种机制来实现线程间的数据同步和异步结果的传递。本章节我们将深入剖析promises的内部工作原理以及如何将它们与futures结合起来使用,展示其在并发编程中的强大功能。
## 3.1 promises基本原理
### 3.1.1 promises的概念
在C++11中,`std::promise`是一个模板类,它可以存储一个值或者一个异常。存储的值或异常可以在将来某个时刻通过`std::future`来检索。`std::promise`通常用于异步计算中,其中一个线程执行一些工作并设置结果,而其他线程通过`std::future`等待并获取这个结果。
```cpp
std::promise<int> prom; // 创建一个int类型的promise对象
std::future<int> fut = prom.get_future(); // 获取与promise关联的future对象
// ... 在另一个线程中执行一些计算 ...
prom.set_value(42); // 设置计算结果,调用线程将阻塞直到结果被获取
std::cout << fut.ge
```
0
0