C++ --- 异步编程
目录
一.什么是异步?同步与异步有什么区别?
1.定义:
2.两者区别:
3.两者优缺点:
4.应用场景:
二.C++异步编程入门:
1.使用future与async进行异步处理:
2.使用future与promise进行异步处理:
3.将异步任务与多线程结合:
三.协程(Coroutines):
1. 什么是协程?
2. 协程的关键概念
3. 实现一个简单的协程:
一.什么是异步?同步与异步有什么区别?
在编程中,同步与异步是两种不同的任务处理方式,主要涉及程序中任务的执行顺序、阻塞方式以及对资源的利用。理解同步与异步的区别,有助于优化程序的执行效率和用户体验。
1.定义:
同步:任务按顺序执行。当前任务执行完成之前,后续任务会等待。
异步:任务的执行可以独立进行,不要求按顺序执行。任务的完成不需要等待前一个任务结束,适用于复杂场景,能提升并发和响应性。
2.两者区别:
特性 | 同步 | 异步 |
---|---|---|
执行顺序 | 按顺序执行 | 不按顺序,可以并行执行 |
资源利用 | 会阻塞资源,浪费等待时间 | 可并发处理,提高资源利用效率 |
性能 | 较低 | 较高 |
常用场景 | 简单任务、必须按顺序执行的任务 | 网络请求、I/O 操作、文件读取等 |
使用难度 | 简单 | 需要更复杂的控制、回调机制等 |
3.两者优缺点:
特性 | 同步 | 异步 |
优点 | 简单易用,代码易于理解 | 资源利用率高,提高程序响应速度 |
缺点 | 可能阻塞,性能较低 | 代码复杂度高,需要管理回调或状态 |
4.应用场景:
同步:适用于简单计算、数据处理、必须按顺序完成的任务,如基础算法、单线程数据存储。
异步:适用于 I/O 密集型任务、网络请求、文件读取等可以并发处理的任务。
- 网络请求:在 Web 应用中,通常会使用异步模型来发送 HTTP 请求,使页面响应更迅速。
- I / O 操作:对于大文件的读写或数据库查询,使用异步模型可以减少等待时间,让 CPU 更高效地处理其他任务。
- 界面刷新:在图形界面应用中,长时间的任务使用异步执行,避免阻塞 UI 线程,提升用户体验。
二.C++异步编程入门:
在 C++ 中,异步编程主要依赖于标准库提供的 std::future
和 std::async
,异步编程允许程序将耗时任务交给其他线程执行,以免主线程被阻塞。结合多线程时,则利用线程池和任务调度器来提高并发效率。异步编程能让程序在等待 I/O 操作、网络请求等耗时任务时,不阻塞主线程,提升程序的响应速度和性能。
1.使用future与async进行异步处理:
首先引入头文件#include<future>
std::future
:表示一个异步任务的结果,是一个占位符,可以在任务完成时获取结果。
std::async
:创建一个异步任务并返回std::future
,它将指定的函数或代码块放到后台执行。像下面代码创建并进行异步调用任务返回结果:(longTask是创建的任务函数,5指的是传入任务的参数)
// 使用 std::async 启动异步任务,获得 future 对象 std::future<int> result = std::async(std::launch::async, longTask, 5);
如果想要获得异步结果并使用的话,使用get()方法获得:(get()方法用于获取异步任务结果,若任务尚未完成,则阻塞主线程等待,直到异步调用返回结果后主线程继续执行)
// 获取异步任务的结果(阻塞,直到任务完成) int value = result.get();
下面是完整的一个异步调用任务的代码示例:
#include <iostream>
#include <future>
#include <chrono>// 模拟耗时任务
int longTask(int x) {std::this_thread::sleep_for(std::chrono::seconds(3)); // 模拟耗时操作return x * x;
}int main() {// 使用 std::async 启动异步任务,获得 future 对象std::future<int> result = std::async(std::launch::async, longTask, 5);// 主线程可以继续执行其他任务std::cout << "Processing in main thread...\n";// 获取异步任务的结果(阻塞,直到任务完成)int value = result.get();std::cout << "Result from async task: " << value << "\n";return 0;
}
2.使用future与promise进行异步处理:
首先引入头文件#include<future>
如果我们想在任务间传递数据,需要手动控制异步结果,这个时候就需要使用future与promise进行处理。
std::promise
和std::future
搭配使用,允许我们在一个线程中设置一个值,然后在另一个线程中异步获取该值。
std::promise:
用于设置任务结果。
std::future:
从中获取任务的结果。
要实现异步调用,我们一般会让
promise
和future
分别在两个线程中使用:
- 创建一个
promise
实例,然后通过get_future()
获取future
。- 启动一个异步线程,在异步线程中使用
promise.set_value()
设置值。- 在主线程中,使用
future.get()
来获取异步线程设置的值。(1)创建 std::promise 对象:
std::promise<int> p;
(2)获取与 promise 关联的 std::future 对象:
std::future<int> future = p.get_future();
(3)启动一个线程执行异步操作,并传递 std::promise 对象:
std::thread t(calculateSquare, std::move(p), 10);
(4)等待异步操作完成,并获取结果:
int result = future.get(); // 这会阻塞直到结果可用
(5)在函数中设置结果:(调用promise内的set_value()方法设置结果)
void calculateSquare(std::promise<int> p, int x) {p.set_value(x * x); // 设置结果 }
这个时候,p内的future对象的值被设置为10*10=100。
下面是基于上面流程的异步调用任务的代码示例:
#include <iostream>
#include <future>
#include <thread>void calculateSquare(std::promise<int> p, int x) {p.set_value(x * x); // 设置结果
}int main() {std::promise<int> p;std::future<int> f = p.get_future(); // 通过 promise 获取 futurestd::thread t(calculateSquare, std::move(p), 10); // 启动线程执行任务t.detach(); // 分离线程std::cout << "Result from thread: " << f.get() << "\n"; // 获取任务结果return 0;
}
我们可以看看promise底层,下面列举出promise部分底层代码:
_EXPORT_STD template <class _Ty>
class promise { // class that defines an asynchronous provider that holds a value
public:// 允许 promise 对象的移动构造,因为需要在异步调用中安全地转移资源。promise(promise&&) = default;// 返回与该 promise 关联的 future 对象,这个 future 对象用于异步获取 promise 设置的值。// 使用 _From_raw_state_tag 标签创建一个 future 对象,内部调用 _MyPromise._Get_state_for_future() 方法获取 future 对象的状态。_NODISCARD_GET_FUTURE future<_Ty> get_future() {return future<_Ty>(_From_raw_state_tag{}, _MyPromise._Get_state_for_future());}// 设置 promise 的值并且通知关联的 future 该值已经可用。void set_value(const _Ty& _Val) {_MyPromise._Get_state_for_set()._Set_value(_Val, false);}void set_value(_Ty&& _Val) {_MyPromise._Get_state_for_set()._Set_value(_STD forward<_Ty>(_Val), false);}// 删除了拷贝构造函数和赋值运算符,禁止 promise 对象的拷贝.// 保证了每个 promise 只能拥有一个关联的 future 对象,不会产生多个引用。promise(const promise&) = delete;promise& operator=(const promise&) = delete;
private:_Promise<_Ty> _MyPromise;
};
3.将异步任务与多线程结合:
C++11 支持 std::thread
类进行多线程编程。结合 std::async
和 std::future
,我们可以构建一个异步多线程任务队列,使得多个任务并行处理。
#include <iostream>
#include <future>
#include <vector>
#include <thread>// 模拟一个计算任务
int computeTask(int x) {std::this_thread::sleep_for(std::chrono::milliseconds(500)); // 模拟任务时间return x * x;
}int main() {// 创建多个异步任务并行执行std::vector<std::future<int>> futures;for (int i = 1; i <= 5; ++i) {// 循环内使用 std::async 启动任务,将 future 对象存入 futures 向量。futures.push_back(std::async(std::launch::async, computeTask, i));}// 获取所有任务结果for (auto& future : futures) {// 迭代 futures 向量,通过 future.get() 获取每个任务的结果。std::cout << "Task result: " << future.get() << "\n";}return 0;
}
三.协程(Coroutines):
1. 什么是协程?
协程是一种特殊的函数,可以在执行中间暂停,保存当前状态,稍后在同样的地方继续执行。这种暂停和恢复功能使得协程可以在同一个线程内执行非阻塞的异步任务。与传统的多线程不同,协程不会消耗系统线程资源,效率更高。
在C++20,协程机制使用 co_await
、co_yield
和 co_return
等关键字,允许我们在函数内部控制暂停和恢复,使得异步代码可以写得像同步代码一样简洁易读。协程的引入极大地简化了复杂异步场景下的代码编写。
2. 协程的关键概念
要理解 C++ 协程,必须掌握以下几个核心概念:
- 协程句柄(coroutine handle):协程的句柄,用来控制协程的生命周期。
co_await
:暂停协程的执行并等待某个值完成。co_yield
:返回一个值给调用者并暂停协程,稍后可以恢复执行。co_return
:结束协程并返回结果。- 协程的状态机:编译器会把协程转换为一个状态机,用来管理协程的暂停、恢复和退出等操作。
3. 实现一个简单的协程:
#include <iostream>
#include <coroutine>
#include <optional>// 定义了协程的返回对象和生命周期管理
struct ReturnObject {// 协程的核心部分,控制协程的开始、结束和异常处理struct promise_type {ReturnObject get_return_object() {return {};}std::suspend_never initial_suspend() {return {};}std::suspend_never final_suspend() noexcept {return {};}void return_void() {}void unhandled_exception() {std::exit(1);}};
};ReturnObject simple_coroutine() {std::cout << "Hello from coroutine\n";// 在协程内使用 co_await std::suspend_always{} 使协程暂停co_await std::suspend_always{}; // 暂停协程std::cout << "Resumed coroutine\n";
}int main() {auto handle = simple_coroutine();std::cout << "Back to main\n";// 协程会在此暂停,需要手动恢复// 在 main() 中调用 handle() 恢复协程执行。handle();std::cout << "Main ends\n";
}
输出结果:
Hello from coroutine
Back to main
// 协程执行 "Hello from coroutine" 后暂停,
// 然后通过句柄恢复继续执行 "Resumed coroutine"。
Resumed coroutine
Main ends