目录
一、std::future 简介
1.1 概念
1.2 应用场景
1.3 关联的方法
1.3.1 std::async
1.3.2 std::package
1.3.3 std::promise
二、future 用法
2.1 使用std::async关联异步任务
2.2 使用std::packaged_task
1. 获取任务结果的机制:
2. 异步任务的管理:
3. 任务与执行环境分离:
4. 线程池和异步任务组合使用:
2.3 使用std::promise
一、std::future 简介
1.1 概念
std::future是C++11标准库中的⼀个模板类,它表示⼀个异步操作的结果。当我们在多线程编程中使⽤异步任务时,std::future可以帮助我们在需要的时候获取任务的执行结果。std::future的⼀个重要特性是能够阻塞当前线程,直到异步操作完成,从⽽确保我们在获取结果时不会遇到未完成的操作。
1.2 应用场景
- 异步任务:当我们需要在后台执⾏⼀些耗时操作时,如网络请求或计算密集型任务等,std::future 可以用来表示这些异步任务的结果。通过将任务与主线程分离,我们可以实现任务的并行处理,从而提⾼程序的执⾏效率
- 并发控制:在多线程编程中,我们可能需要等待某些任务完成后才能继续执⾏其他操作。通过使⽤std::future,我们可以实现线程之间的同步,确保任务完成后再获取结果并继续执⾏后续操作
- 结果获取:std::future提供了⼀种安全的⽅式来获取异步任务的结果。我们可以使用std::future::get() 函数来获取任务的结果,此函数会阻塞当前线程,直到异步操作完成。这样,在调用 get() 函数时,我们可以确保已经获取到了所需的结果
1.3 关联的方法
1.3.1 std::async
std::async 是一个函数模板:它会根据用户设置的传入参数选择不同的方案,异步执行一个函数,返回一个 future 对象用于获取函数结果。
1.3.2 std::package
std::packaged_task 是一个类模板:为一个函数生成一个异步任务对象,用于在其他线程中执行。是对函数的二次封装。
1.3.3 std::promise
std::promise 是一个类模板:实例化的对象可以返回一个 future ,在其他线程中向 promise 对象设置数据,其他线程的关联 future 就可以获取数据。是对结果的封装。
二、future 用法
2.1 使用std::async关联异步任务
std::async 是⼀种将任务与 std::future 关联的简单⽅法。它创建并运行⼀个异步任务,并返回⼀个与该任务结果关联的 std::future 对象。
默认情况下, std::async 是否启动⼀个新线程,或者在等待 future 时,任务是否同步运行都取决于你给的参数。
这个参数为 std::launch 类型,以下介绍关于 std::launch 的三种策略:
std::launch::async
策略:
- 这个策略会立即启动一个新线程来执行异步任务。任务的执行是非阻塞的,意味着你可以在等待任务完成的同时继续执行其他代码。
- 当你调用
future.get()
时,如果任务还未完成,程序会阻塞,直到异步任务完成并返回结果。
std::launch::deferred
策略:
- 这个策略不会立即创建线程执行任务,而是将任务推迟到你调用
future.get()
时才执行。换句话说,任务的执行是懒加载的方式,只有在你需要结果时,才会真正开始执行任务。- 在此模式下,任务的执行是同步的,意味着在你调用
get()
时任务才会运行,且在运行结束之前程序会被阻塞。因此,在调用get()
之前不会创建新线程,所有任务都在调用get()
的线程中完成。
std::launch::async
|std::launch::deferred
策略:
- 内部通过系统等条件⾃动选择策略
#include <iostream>
#include <future>int Add(int num1, int num2)
{return num1 + num2;
}int main()
{// std::launch::async策略:内部创建一个线程执行函数,函数运行结果通过future获取// std::launch::deferred策略:同步策略,获取结果的时候再去执行任务std::future<int> res = std::async(std::launch::deferred, Add, 11, 22); // 进行了一个异步非阻塞调用// std::future<int>::get() 用于获取异步任务的结果,如果还没有结果就会阻塞std::cout << res.get() << std::endl;return 0;
}
2.2 使用std::packaged_task
std::packaged_task就是将任务和 std::feature 绑定在⼀起的模板,是⼀种对任务的封装。
我们可以通过 std::packaged_task 对象获取任务相关联的 std::future 对象,通过调用 get_future() 方法获得。
总结下来使用 std::packaged_task 的步骤就是:
1. 使用 package_task 封装需要异步操作的方法,可以使用 shared_ptr 进一步封装方便管理生命周期
auto task = std::make_shared < std::packaged_task < 返回值 ( 传入参数 ) > > ( 函数名 ) ;
2. 使用 future 模板获取 package_task 关联的 future 对象
std::future < 返回值类型 > res = task -> get_future() ;
3. 创建新线程执行异步操作的函数
4. 使用 future 的 get() 方法获取函数执行的结果
res.get()
#include <iostream>
#include <future>
#include <thread>
#include <memory>int Add(int num1, int num2)
{return num1 + num2;
}int main()
{// 1. 封装任务auto task = std::make_shared<std::packaged_task<int(int, int)>>(Add);// 2. 获取任务包关联的future对象std::future<int> res = task->get_future();std::thread thr([task](){ (*task)(11, 22); });// 3. 获取结果std::cout << res.get() << std::endl;thr.join();return 0;
}
观察了以上代码,你可能会有疑问,既然使用 future 与 package_task 也要新创建线程,那么为什么不直接创建新线程执行回调函数呢?下面会解答这个疑问:
使用
std::packaged_task
和std::future
的主要目的是为任务的结果获取机制提供更方便的方式,尤其是在任务和结果分离的场景下,而不仅仅是启动一个线程。这与直接使用std::thread
的方式相比,有以下几个关键优势:1. 获取任务结果的机制:
std::packaged_task
会将一个任务(函数)与std::future
绑定在一起,因此你可以通过future.get()
来获取任务的执行结果。- 当你只使用
std::thread
时,线程完成的任务结果必须通过其他方式传递,比如共享状态、回调函数等,这些方式通常更复杂且容易出错。而std::future
提供了一个更安全和简单的接口。2. 异步任务的管理:
- 使用
std::future
可以轻松等待异步任务完成,通过调用future.get()
可以阻塞当前线程,直到任务完成并返回结果。而使用std::thread
时,没有一个简单的方法来阻塞线程并获取任务结果,必须自己处理同步机制。std::future
提供了一种更标准化的机制来处理任务的执行和结果获取,这使得代码的可读性和可维护性更好。3. 任务与执行环境分离:
std::packaged_task
和std::future
将任务(Add
函数)与它的执行环境(线程)解耦。你可以在任何时候启动任务并获取结果,而不必担心线程的创建和管理。这为任务调度提供了更大的灵活性,比如可以通过不同的方式执行任务:立即执行、推迟执行或在线程池中执行。- 直接使用
std::thread
需要你手动管理线程的生命周期,比如什么时候开始、什么时候结束、如何获取结果等。4. 线程池和异步任务组合使用:
- 在一些场景下,你可能会将任务提交到一个线程池中,而不是直接创建线程。在这种情况下,使用
std::packaged_task
和std::future
可以非常方便地处理任务的提交和结果获取,而不需要手动管理线程的创建和销毁。- 例如,你可以将
std::packaged_task
提交给线程池,而future
则用来等待并获取任务的结果。
2.3 使用std::promise
在使用 promise 时,先实例化一个 promise 对象,然后实例化 future 对象接收 promise 对象关联的 future 对象,这样就实现了 promise 与 future 的绑定,然后后续通过对 promise 对象做修改, future 对象就可以获得异步操作函数的返回值
#include <iostream>
#include <future>
#include <thread>
#include <memory>int Add(int num1, int num2)
{return num1 + num2;
}int main()
{// 1. 在使用的时候,就是先实例化一个指定结果的promise对象,std::promise<int> pro;// 2. 通过promise对象,获取关联的future对象std::future<int> res = pro.get_future();// 3. 在任意位置给promise设置数据,就可以通过关联的future获取到这个设置的数据了std::thread thr([&pro](){int sum = Add(11 , 22);pro.set_value(sum); });std::cout << res.get() << std::endl;thr.join();return 0;
}