目录
一、前言
二、什么是异步操作呢?
🔥异步的概念🔥
🔥异步的生活案例说明🔥
三、异步有那些操作呢?
🔥std::future🔥
💢std::future 的概念💢
💢主要成员函数💢
💧std::future 与 std::async 💧
💧std::future 与 std::packaged_task💧
💧std::future与std::promise💧
四、总结
五、共勉
一、前言
如何让程序更高效、更快速一直是开发者追求的目标。【C++11】 引入的异步操作,为我们提供了一个解决这个问题的新方式。通过使用
std::async
和std::future
等工具,我们可以轻松地处理并发任务,让程序在处理复杂操作时不再卡顿。
本篇文章将带你轻松入门 【C++11】 的异步操作,从基础概念到实际应用,逐步讲解如何利用这些新工具编写更高效的程序。
二、什么是异步操作呢?
🔥异步的概念🔥
异步操作 与 同步操作 相对。在同步操作中,程序必须按顺序执行各个任务,等待前一个任务完成后才能继续执行下一个任务。在异步操作中,程序可以启动一个耗时任务,然后继续执行其他任务,而无需等待该耗时任务完成。当耗时任务完成后,程序会得到通知并处理结果。
🔥异步的生活案例说明🔥
点餐和上菜
想象你在餐厅点餐的过程,这是一个很好理解异步概念的例子。
同步操作的情况:
- 你走进餐厅,点了一份牛排。
- 你站在柜台前等着,直到牛排做好并送到你手上,你才能去找座位开始吃饭。
在这个例子中:
顾客(主线程)发起一个任务(子线程做牛排),做牛排的过程中顾客没去做别的事情而是死等,这就是一条时间线(同步),此时效率相对较低
异步操作的情况:
- 你走进餐厅,点了一份牛排。
- 你点完餐后,服务员给你一个号牌,你可以去找座位坐下。
- 在等待牛排做好期间,你可以先喝点水,聊聊天,或者刷刷手机。
- 当牛排做好后,服务员会叫号或者送到你桌前,你再开始吃饭。
在这个例子中:
顾客(主线程)发起一个任务(子线程做牛排),做牛排的过程中顾客去做的别的事情,有两条时间线(异步),此时效率相对较高
三、异步有那些操作呢?
由于 多线程程序中的任务大都是异步 的,主线程 和 子线程 分别执行不同的任务,如果想要在主线中得到某个子线程任务函数返回的结果可以使用【C++11】提供的 std::future类,这个类需要和其他类或函数搭配使用。
🔥std::future🔥
💢std::future 的概念💢
std::future 是C++11标准库中的⼀个模板类,它表⽰⼀个异步操作的结果(完成线程间的通信)。当我们在多线程编程中使⽤ 异步任务时,std::future 可以帮助我们在需要的时候获取任务的执⾏结果。std::future 的⼀个重要特性 是能够阻塞当前线程,直到异步操作完成,从⽽确保我们在获取结果时不会遇到未完成的操作。
它主要与std::async
、std::promise
和std::packaged_task
一起使用。
所需的头文件:
#include <future>
💢主要成员函数💢
get()
get()
是std::future
最重要的成员函数。它用于获取异步操作的结果。如果结果还不可用,get()
会阻塞调用线程,直到结果可用。
T get();
- 如果异步操作成功完成,
get()
返回结果。 - 如果异步操作抛出异常,
get()
重新抛出该异常。
valid()
valid()
用于检查std::future
是否有一个与之关联的共享状态。如果std::future
还没有获取过结果或者还没有被移动,它返回true
。
bool valid() const noexcept;
wait()
wait()
用于阻塞调用线程,直到异步操作完成。
void wait() const;
wait_for()
wait_for()
用于阻塞调用线程,直到异步操作完成或者指定的时间段过去。
template< class Rep, class Period >
std::future_status wait_for( const std::chrono::duration<Rep, Period>& rel_time ) const;
返回值:
std::future_status::ready
:异步操作已完成。std::future_status::timeout
:超时时间已到,但异步操作尚未完成。-
std::future_status::deferred
:异步操作被延迟(即std::launch::deferred
)。
下面我们将会 结合std::async
、std::promise
和 std::packaged_task
举例说明 std::future 的成员函数的各种用法
💧std::future 与 std::async 💧
std::async
是 【C++11】 引入的一个函数模板,用于启动异步任务。它允许你将某个函数的调用放到一个独立的线程中执行,并返回一个std::future
对象来获取函数的结果。
语法
#include <future>
template< class Function, class... Args >
std::future<typename std::result_of<Function(Args...)>::type>
async( std::launch policy, Function&& f, Args&&... args );
template< class Function, class... Args >
std::future<typename std::result_of<Function(Args...)>::type>
async( Function&& f, Args&&... args );
policy
:启动策略,可以是std::launch::async
、std::launch::deferred
,或者是它们的组合。std::launch::async
:在新线程中异步执行任务。std::launch::deferred
:延迟执行任务,直到调用future
的get
或wait
成员函数。
f
:要执行的函数。args
:传递给函数f
的参数。
返回值
返回一个 std::future
对象,用于获取异步任务的结果。
异步任务的启动策略
std::launch::async
:强制在新线程中异步执行任务。std::launch::deferred
:延迟执行任务,直到调用std::future
的get
或wait
成员函数。- 默认行为:如果未指定
policy
,则系统将自动选择合适的策略。
举例说明: 模拟一个去餐厅点牛排的过程 进行异步操作
#include <iostream>
#include <thread>
#include <future>
#include <chrono>
// 模拟一个耗时操作的函数
int cook_steak(int timesteak)
{
std::cout << "make steak" << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(timesteak)); // 模拟做牛排的时间
return 1; // 假设 1 代表牛排已做好
}
int main()
{
// 创建一个 packaged_task 对象,封装耗时操作
std::packaged_task<int(int)> task(cook_steak);
// 获取与 packaged_task 相关联的 future 对象
std::future<int> result = task.get_future();
// 启动任务,任务将在当前线程执行
std::thread task_thread(std::move(task), 5);
// 在等待牛排的同时,可以做其他事情 每过一秒检查一次
while (result.wait_for(std::chrono::seconds(1)) != std::future_status::ready)
{
std::cout << "...Doing other things while waiting for the steak...\n";
}
// 等待任务完成并获取结果
int steak_status = result.get();
if (steak_status == 1)
{
std::cout << "Steak is ready! Time to eat.\n";
}
// 确保任务线程完成
task_thread.join();
return 0;
}
输出结果:
make steak
...Doing other things while waiting for the steak...
...Doing other things while waiting for the steak...
...Doing other things while waiting for the steak...
...Doing other things while waiting for the steak...
...Doing other things while waiting for the steak...
Steak is ready! Time to eat.
此时我们发现,子线程 和 主线程 同时执行 在子线程运行的时候,主线程可以做其它的事情,而不受子线程的影响。 这就是 ---- 异步操作
如果将 async 模式 改为 deferred 模式
deferred 模式 它表示任务将被推迟到调用 get()
或 wait()
时才执行。简单来说,deferred
模式不会在任务启动时立即执行任务,而是将任务的执行推迟到需要结果时。
#include <iostream>
#include <thread>
#include <future>
#include <chrono>
// 模拟一个耗时操作的函数
int cook_steak(int timesteak)
{
std::cout << "make steak" << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(timesteak)); // 模拟做牛排的时间
return 1; // 假设 1 代表牛排已做好
}
int main()
{
// 启动异步任务做牛排
std::future<int> result = std::async(std::launch::deferred, cook_steak , 5);
std::cout << "...Doing other things while waiting for the steak...\n";
// 获取异步任务的结果(即牛排是否做好)
// std::future<int>::get() 用于获取异步任务的结果,如果没有结果就会阻塞
int steak_status = result.get();
if (steak_status == 1)
{
std::cout << "Steak is ready! Time to eat.\n";
}
return 0;
}
输出结果:
...Doing other things while waiting for the steak...
make steak
Steak is ready! Time to eat.
我们发现,deferred
模式不会在任务启动时立即执行任务,而是将任务的执行推迟到需要结果时。
💧std::future 与 std::packaged_task💧
std::packaged_task
是 C++11 提供的一个工具,用于将任何可调用对象(如函数、函数对象、lambda 表达式等)封装成一个任务,然后可以通过std::future
来获取任务的结果。它可以用于异步操作和线程操作,将任务与结果进行关联。
std::packaged_task
的基本使用
- 定义任务:封装一个任务,例如一个函数或 lambda 表达式。
- 创建
std::packaged_task
对象:将任务封装到std::packaged_task
对象中。 - 获取
std::future
对象:通过std::packaged_task::get_future()
方法获取std::future
对象,用于获取任务的结果。 - 启动任务:通过
std::thread
或直接调用operator()
启动任务。 - 获取结果:通过
std::future::get()
方法获取任务的结果。
举例说明: 模拟一个去餐厅点牛排的过程 进行异步操作
#include <iostream>
#include <thread>
#include <future>
#include <chrono>
// 模拟一个耗时操作的函数
int cook_steak(int timesteak)
{
std::cout << "make steak" << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(timesteak)); // 模拟做牛排的时间
return 1; // 假设 1 代表牛排已做好
}
int main()
{
// 创建一个 packaged_task 对象,封装耗时操作
std::packaged_task<int(int)> task(cook_steak);
// 获取与 packaged_task 相关联的 future 对象
std::future<int> result = task.get_future();
// 启动任务,任务将在当前线程执行
std::thread task_thread(std::move(task), 5);
// 主线程可以继续执行其他操作
std::cout << "...Doing other things while waiting for the steak...\n";
// 等待任务完成并获取结果
int steak_status = result.get();
if (steak_status == 1)
{
std::cout << "Steak is ready! Time to eat.\n";
}
// 确保任务线程完成
task_thread.join();
return 0;
}
输出结果:
make steak
...Doing other things while waiting for the steak...
...Doing other things while waiting for the steak...
...Doing other things while waiting for the steak...
...Doing other things while waiting for the steak...
...Doing other things while waiting for the steak...
Steak is ready! Time to eat.
总结:
- 异步操作:由于
std::thread
用于在新线程中执行任务,因此代码中确实存在异步操作。主线程和子线程并行执行任务和其他操作。 - 子线程生成:
std::thread
的使用确保了新线程的创建,这意味着cook_steak(5)
会在子线程中执行,而主线程则继续执行其他操作。
关键点:异步操作是通过 std::thread
来实现的,确保了任务在子线程中并发执行,而主线程可以在此期间进行其他操作。
💧std::future与std::promise💧
std::promise
是 C++ 标准库中用于与std::future
配合的一个类,用于在异步操作中设置共享状态。它允许你在一个线程中设置结果,并在另一个线程中读取这个结果。
std::promise
的作用
std::promise
提供了一个接口,用于设置一个异步操作的结果。它与std::future
一起使用,std::future
用于获取这个结果。- 典型用法:你通常在一个线程中创建一个
std::promise
对象,并将它的std::future
传递给另一个线程。第一个线程会设置结果,而第二个线程则会获取这个结果。
#include <iostream>
#include <thread>
#include <future>
#include <chrono>
// 模拟一个耗时操作的函数
void cook_steak(std::promise<int> prom, int timesteak)
{
std::cout << "make steak" << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(timesteak)); // 模拟做牛排的时间
prom.set_value(1); // 设置 promise 的结果
}
int main()
{
// 创建一个 promise 对象
std::promise<int> prom;
// 获取与 promise 相关联的 future 对象
std::future<int> result = prom.get_future();
// 启动线程来执行任务
std::thread task_thread(cook_steak, std::move(prom), 5);
// 在等待牛排的同时,可以做其他事情 每过一秒检查一次
while (result.wait_for(std::chrono::seconds(1)) != std::future_status::ready)
{
std::cout << "...Doing other things while waiting for the steak...\n";
}
// 获取异步任务的结果(即牛排是否做好)
int steak_status = result.get(); // 这里会阻塞直到任务设置值
if (steak_status == 1)
{
std::cout << "Steak is ready! Time to eat.\n";
}
// 确保任务线程完成
task_thread.join();
return 0;
}
输出结果:
make steak
...Doing other things while waiting for the steak...
...Doing other things while waiting for the steak...
...Doing other things while waiting for the steak...
...Doing other things while waiting for the steak...
...Doing other things while waiting for the steak...
Steak is ready! Time to eat.
总结
std::promise
允许你在一个线程中设置结果,另一个线程中获取这个结果。std::future
用于获取std::promise
设置的值,并可以在需要时阻塞当前线程直到结果可用。- 使用
std::promise
和std::future
的主要好处是它们提供了清晰的异步任务通信机制,使得任务结果的传递变得简单和安全。
四、总结
1️⃣: 使用async()函数,是多线程操作中最简单的一种方式,不需要自己创建线程对象,并且可以得到子线程函数的返回值。
2️⃣:使用std::promise类,在子线程中可以传出返回值也可以传出其他数据,并且可选择在什么时机将数据从子线程中传递出来,使用起来更灵活。
3️⃣:使用std::packaged_task类,可以将子线程的任务函数进行包装,并且可以得到子线程的返回值。
五、共勉
以下就是我对 【C++11】异步操作 的理解,如果有不懂和发现问题的小伙伴,请在评论区说出来哦,同时我还会继续更新对 【C++11】 的理解,请持续关注我哦!!!