时间:2025-05-21 10:04
人气:
作者:admin
async_resolve({host, port}, [](auto endpoint){
async_connect(endpoint, [](auto error_code){
async_handle_shake([](auto error_code){
send_data_ = build_request();
async_write(send_data_, [](auto error_code){
async_read();
});
});
});
});
void async_read() {
async_read(response_, [](auto error_code){
if(!finished()) {
append_response(recieve_data_);
async_read();
}else {
std::cout<<"finished ok\n";
}
});
}
基于异步回调的 client 流程如下:
auto endpoint = co_await async_query({host, port});
auto error_code = co_await async_connect(endpoint);
error_code = co_await async_handle_shake();
send_data = build_request();
error_code = co_await async_write(send_data);
while(true) {
co_await async_read(response);
if(finished()) {
std::cout<<"finished ok\n";
break;
}
append_response(recieve_data_);
}
同样是异步 client,相比回调模式的异步 client,整个代码非常清爽,简单易懂,同时保持了异步的高性能,这就是 C++20 协程的威力!
协程分为无栈协程和有栈协程两种
有栈协程:每个协程创建的时候都会获得一块固定大小 (如 128k) 的堆内存,协程运行的时候就是使用这块堆内存当作运行栈使用,切换时候保存/恢复运行栈和相应寄存器
无栈协程:实现原理并不是通过切换时保存/恢复运行栈和寄存器实现的,它的实现见下,由于协程的每个中断点都是确定,那其实只需要将函数的代码再进行细分,保存好局部变量,做好调用过程的状态变化。例如:
void fn(){
int a, b, c;
a = b + c;
yield();
b = c + a;
yield();
c = a + b;
}
将上面的代码自动转换为以下形式:
Struct fn{
int a, b, c;
int __state = 0;
void resume(){
switch(__state) {
case 0:
return fn1();
case 1:
return fn2();
case 2:
return fn3();
}
}
void fn1(){
a = b + c;
}
void fn2(){
b = c + a;
}
void fn3(){
c = a + b;
}
};
上面就将一个协程函数 fn 进行切分后变成一个Struct,这样的实现相对于有栈协程而言使用的内存更少。当然上面只是一种演示,对应早期的 reenter 用法,这个宏底层通过 switch-case 将函数拆分成多个可重入点,一般也称为 duff device。
难于理解、过于灵活、动态分配导致的性能问题等等。 C++20 协程关键概念繁多:
C++20 协程运行流程图:
另一个视角:
await 流程:
目前只适合给库作者使用,因为它只提供了一些底层的协程原语和一些协程暂停和恢复的机制,普通用户如果希望使用协程只能依赖协程库,由协程库来屏蔽这些底层细节,提供简单易用的 API,以便业务侧使用负担尽可能低。
选取一个合适的协程库有助于屏蔽 C++20 底层的实现细节,对用户更加友好,目前市面上有以下几种选择:
#include <asio/co_spawn.hpp>
#include <asio/detached.hpp>
#include <asio/io_context.hpp>
#include <asio/ip/tcp.hpp>
#include <asio/signal_set.hpp>
#include <asio/write.hpp>
#include <cstdio>
#include <iostream>
using asio::ip::tcp;
using asio::awaitable;
using asio::co_spawn;
using asio::detached;
using asio::use_awaitable;
namespace this_coro = asio::this_coro;
#if defined(ASIO_ENABLE_HANDLER_TRACKING)
# define use_awaitable \
asio::use_awaitable_t(__FILE__, __LINE__, __PRETTY_FUNCTION__)
#endif
awaitable<void> echo(tcp::socket socket)
{
try
{
char data[1024];
for (;;)
{
std::size_t n = co_await socket.async_read_some(asio::buffer(data), use_awaitable);
co_await async_write(socket, asio::buffer(data, n), use_awaitable);
}
}
catch (std::exception& e)
{
std::printf("echo Exception: %s\n", e.what());
}
}
void fn2(){
std::cout<<"hhh\n";
}
void fn(){
fn2();
}
awaitable<void> listener()
{
auto executor = co_await this_coro::executor;
fn();
tcp::acceptor acceptor(executor, {tcp::v4(), 8988});
for (;;)
{
tcp::socket socket = co_await acceptor.async_accept(use_awaitable); //调用协程,体现同步性
co_spawn(executor, echo(std::move(socket)), detached);// 创建连接处理线程
}
}
int main()
{
try
{
asio::io_context io_context(1);
asio::signal_set signals(io_context, SIGINT, SIGTERM);
signals.async_wait([&](auto, auto){ io_context.stop(); });
co_spawn(io_context, listener(), detached); // 创建纤程,体现并发性
io_context.run(); // 开始调度
}
catch (std::exception& e)
{
std::printf("Exception: %s\n", e.what());
}
}
[1]. 在 Boost.Asio 中使用协程
[2]. C++20协程原理和应用
[3]. C++网络编程之asio(五)——在asio中使用协程
[4]. C++20协程不完全指南
[5]. 深入浅出c++协程
[7]. 聊聊协程的发展历程
[8]. asio服务器模式:协程
[10]. Boost中的协程—Boost.Asio中的coroutine类
[11]. 如何在C++17中实现stackless coroutine以及相关的任务调度器
[12]. C++20 Coroutine实例教学
[13]. 译:你的第一个协程程序(Your first coroutine)
[14]. ASIO 与协程
[15]. C++ compiler support
本文来自博客园,作者:goodcitizen,转载请注明原文链接:https://www.cnblogs.com/goodcitizen/p/18887511/reduce_the_complexity_of_network_programming_asynchronously_with_cpp20_coroutines