【CPP】std::future

概述: std::future 的用法整理

[toc]

介绍

1
2
3
#include <future>

std::future < T > f

std::future是C++11标准库(并发支持库)中的一个[模板类],它表示一个异步操作的结果。当我们在多线程编程中使用异步任务时,std::future可以帮助我们在需要的时候获取任务的执行结果。std::future的一个重要特性是能够阻塞当前线程,直到异步操作完成,从而确保我们在获取结果时不会遇到未完成的操作。

成员函数

  • 构造函数
    • future() noexcept: 默认构造函数。构造无共享状态的 std::future 。构造后, valid() == false
    • future( future&& other ) noexcept:移动构造函数。用移动语义,以 other 的共享状态构造 std::future 。构造后 other.valid() == false 。
    • future( const future& other ) = delete : 不可复制构造 (CopyConstructible) 。
  • 析构函数 ~future();
  • operator=
    • future& operator=( future&& other ) noexcept:释放任何共享状态并移动赋值 other 的内容给 *this 。赋值后, other.valid() == false 且 this->valid() 将产生与 other.valid() 在赋值前相同的值。
    • future& operator=( const future& other ) = delete: 不可复制赋值 (CopyAssignable) 。
  • share() noexcept: 转移 *this 的共享状态到 std::shared_future 对象。多个 std::shared_future 对象可引用同一共享对象,调用后valid() == false
  • T get(): get 方法等待直至 future 拥有合法结果并(依赖于使用哪个模板)获取它。它等效地调用 wait() 等待结果。泛型模板和二个模板特化各含单个 get 版本。 get 的三个版本仅在返回类型有别。若调用此函数前 valid() 为 false 则行为未定义。释放任何共享状态。调用此方法后valid() 为 false 。
  • bool valid(): 检查future是否可以共享状态。
  • wait(): 等待结果变得可用
  • wait_for
  • wait_until

作用

  1. 异步操作的结果获取std::future提供了一种机制,允许我们在多线程环境中安全地获取异步操作的结果。
  2. 隐藏异步操作的细节std::future将异步操作的结果封装起来,使程序员无需关注线程同步和通信的具体实现细节。
  3. 线程同步:通过阻塞等待异步操作完成,std::future可以确保我们在继续执行其他操作之前,已经获取了所需的结果。
  4. 异常处理std::future可以捕获异步操作中抛出的异常,并在获取结果时重新抛出,也可以在主线程中对其进行处理,从而使得异常处理更加简单。
  5. 提高性能std::future使得我们能够更好地利用多核处理器的性能,通过并行执行任务来提高程序的执行效率。

使用场景

异步任务

当我们需要在后台执行一些耗时操作时,如[文件读写]、网络请求或计算密集型任务,std::future可以用来表示这些异步任务的结果。通过将任务与主线程分离,我们可以实现任务的并行处理,从而提高程序的执行效率。

并发控制

在[多线程编程]中,我们可能需要等待某些任务完成后才能继续执行其他操作。通过使用std::future,我们可以实现线程之间的同步,确保任务完成后再获取结果并继续执行后续操作。

结果获取

std::future提供了一种安全的方式来获取异步任务的结果。我们可以使用std::future::get()函数来获取任务的结果。此函数会阻塞当前线程,直到异步操作完成。这样,在调用get()函数时,我们可以确保已经获取到了所需的结果。如果异步操作中发生了异常,get()函数会将异常重新抛出,使得我们能够处理这些异常。

用法示例

使用std::async关联异步任务

使用std::async将任务与std::future关联。它创建并运行一个异步任务,并返回一个与该任务结果关联的std::future对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
#include <future>
#include <chrono>

int long_running_task() {
std::this_thread::sleep_for(std::chrono::seconds(3));
return 42;
}

int main() {
// std::future<int> 声明的类型与 long_running_task 返回值类型必须一致
std::future<int> result_future = std::async(std::launch::async, long_running_task);

// 在此处执行其他操作

int result = result_future.get();
std::cout << "Result: " << result << std::endl;

return 0;
}

使用std::promise与std::future配合

std::promise是另一种与std::future配合使用的方式。我们可以使用std::promise对象来显式地设置任务的结果,而std::future对象则用于获取这个结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <iostream>
#include <future>
#include <thread>

void long_running_task(std::promise<int> result_promise) {
// 执行长时间运行的任务
int result = 42;

// 将结果设置到promise对象中
result_promise.set_value(result);
}

int main() {
std::promise<int> result_promise;
std::future<int> result_future = result_promise.get_future();

// 创建一个新线程,执行长时间运行的任务
std::thread task_thread(long_running_task, std::move(result_promise));

// 在此处执行其他操作

int result = result_future.get();
std::cout << "Result: " << result << std::endl;

task_thread.join();

return 0;
}

结果获取与异常处理

使用std::future::get() 函数可以获取异步任务的结果。此函数会阻塞当前线程,直到异步操作完成。如果异步操作中发生了异常,get()函数会将异常重新抛出。

⚠️ 注意:std::future::get()函数只能被调用一次。在调用get()之后,std::future对象将变为无效状态。

🚧 如果需要多次访问结果,可以考虑使用std::shared_future。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
try {
int result = result_future.get();
std::cout << "Result: " << result << std::endl;
} catch (const std::exception& e) {
std::cerr << "Exception caught: " << e.what() << std::endl;
} catch (...) {
std::cerr << "Unknown exception caught" << std::endl;
}

std::chrono::milliseconds timeout(100);
std::future_status status = result_future.wait_for(timeout);

if (status == std::future_status::ready) {
int result = result_future.get();
std::cout << "Result: " << result << std::endl;
} else {
std::cerr << "Timeout: the task is still running" << std::endl;
}

注意事项

在使用std::future时,需要注意以下几点:

  1. std::future::get()函数只能被调用一次。调用get()之后,std::future对象将变为无效状态。如果需要多次访问结果,可以考虑使用std::shared_future
  2. std::future对象不可拷贝,但可以通过移动构造函数或移动赋值操作符进行转移。这意味着我们不能将std::future对象存储在容器中,除非使用std::shared_future或指针包装。
  3. 当使用std::async时,默认情况下,任务可能会在当前线程上下文中执行。这取决于库实现和系统资源。若要确保任务在新线程中执行,可以使用std::launch::async标志:

std::future result_future = std::async(std::launch::async, long_running_task);

  1. 如果std::future对象在析构时仍关联着一个有效的异步操作,且该操作尚未完成,析构函数会阻塞等待操作完成。在某些情况下,这可能导致程序死锁。为了避免这个问题,可以在析构std::future对象之前显式地调用wait()、wait_for()或wait_until()函数。
  2. 虽然std::future在获取异步任务结果和线程同步方面非常有用,但它并不能解决所有并发问题。例如,std::future无法用于实现复杂的并发模式,如线程池、工作窃取等。对于这些高级并发需求,可能需要使用其他库或者自定义实现。
  3. 使用std::promisestd::future时,需要确保在调用get()之前已经设置了对应的值,否则会导致未定义的行为。这可能需要对代码进行仔细的设计和调试,以确保正确的执行顺序。
  4. std::future对象的生命周期需要仔细管理。如果std::future对象被提前销毁,那么关联的异步任务可能会变得不可访问。因此,需要确保在异步任务完成之前保持std::future对象的有效性。
  5. 在某些情况下,使用std::future可能会导致性能下降。例如,当多个线程频繁地等待异步操作结果时,可能会导致线程阻塞和上下文切换开销。为了避免这个问题,可以考虑使用非阻塞的方式查询异步操作状态,如std::future::wait_for()std::future::wait_until()函数。这样,我们可以在异步操作未完成时执行其他任务,提高程序的响应性和并发性能。
  6. std::future并不适用于所有场景。例如,它不适用于需要处理多个输入或输出结果的任务,或者需要实现动态任务依赖关系的场景。在这些情况下,可能需要寻找其他并发和同步解决方案。
  7. 虽然std::future提供了异常处理机制,但需要注意的是,一旦std::future::get()函数重新抛出异常,该异常需要在调用get()的线程中进行捕获和处理。这意味着需要在主线程和其他线程中设置适当的异常处理策略,以确保程序的稳定性和健壮性。

其他

std::shared_future

std::shared_futurestd::future的一个变体,允许多个线程共享同一个异步操作的结果。与std::future不同,std::shared_future对象可以被拷贝,因此可以将其存储在容器中或在多个线程之间传递。此外,std::shared_future::get()函数可以被多次调用,而不会使std::shared_future对象变为无效状态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
#include <future>
#include <thread>

void print_result(std::shared_future<int> result_future) {
int result = result_future.get();
std::cout << "Result: " << result << std::endl;
}

int main() {
std::promise<int> result_promise;
std::shared_future<int> result_future = result_promise.get_future().share();

std::thread t1(print_result, result_future);
std::thread t2(print_result, result_future);

result_promise.set_value(42);

t1.join();
t2.join();

return 0;
}

std::future_status

std::future_status是一个枚举类型,表示异步操作的状态。它有三个可能的值:

  • std::future_status::ready:异步操作已完成,结果可用。
  • std::future_status::timeout:异步操作尚未完成,等待已超时。
  • std::future_status::deferred:异步操作已被延迟,尚未启动(仅当使用std::launch::deferred策略创建std::future对象时才可能出现)。

我们可以使用std::future::wait_for()std::future::wait_until()函数查询异步操作的状态,而无需阻塞当前线程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <iostream>
#include <future>
#include <chrono>

int long_running_task() {
std::this_thread::sleep_for(std::chrono::seconds(3));
return 42;
}

int main() {
std::future<int> result_future = std::async(std::launch::async, long_running_task);

std::chrono::milliseconds timeout(1000);
std::future_status status = result_future.wait_for(timeout);

if (status == std::future_status::ready) {
int result = result_future.get();
std::cout << "Result: " << result << std::endl;
} else {
std::cerr << "Timeout: the task is still running" << std::endl;
}
// 等待任务完成,以便正确获取结果
int result = result_future.get();
std::cout << "Final result: " << result << std::endl;

return 0;
}

在上面的示例中,我们使用了std::future::wait_for()函数来查询异步操作的状态。如果状态为std::future_status::ready,我们可以立即获取结果。否则,我们可以在等待期间执行其他任务,以提高程序的响应性和并发性能。最后,在退出main函数之前,我们会调用result_future.get()以确保正确获取异步操作的结果。


【CPP】std::future
https://hodlyounger.github.io/B_Code/CPP/std/【CPP】future/
作者
mingming
发布于
2023年10月27日
许可协议