从零构建现代C++ Web服务器(四):实战案例与性能调优

从零构建现代C++ Web服务器(四):实战案例与性能调优 系列导航:第一篇:设计理念 | 第二篇:协程与内存池 | 第三篇:路由、中间件与SSL | 第四篇:实战与性能(本文) | 第五篇:Cookie、Session与文件服务 | 第六篇:数据库中间件 前置知识 阅读过本系列前三篇 了解 RESTful API 基本概念 了解 JSON 序列化/反序列化 目录 1. 完整案例:RESTful API 服务 2. 完整案例:WebSocket 实时通信 3. 反射宏系统 4. 性能调优实战 5. 安全加固清单 6. 错误处理体系 7. 系列总结 1. 完整案例:RESTful API 服务 1.1 从零构建用户管理 API 让我们用 hical 构建一个完整的用户管理 REST API,包含路由注册、中间件、JSON 请求/响应和路径参数。 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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 #include "core/HttpServer.h" #include <boost/json.hpp> #include <iostream> using namespace hical; namespace json = boost::json; int main() { HttpServer server(8080); // ============ 中间件 ============ // 日志中间件:记录请求方法、路径和响应状态码 server.use([](HttpRequest& req, MiddlewareNext next) -> Awaitable<HttpResponse> { auto start = std::chrono::steady_clock::now(); std::cout << httpMethodToString(req.method()) << " " << req.path() << std::endl; auto res = co_await next(req); auto elapsed = std::chrono::duration_cast<std::chrono::microseconds>( std::chrono::steady_clock::now() - start).count(); std::cout << " -> " << static_cast<int>(res.statusCode()) << " (" << elapsed << "us)" << std::endl; co_return res; }); // 认证中间件(简化版) server.use([](HttpRequest& req, MiddlewareNext next) -> Awaitable<HttpResponse> { // 公开路由不需要认证 if (req.path() == "/" || req.path() == "/api/status") { co_return co_await next(req); } auto authHeader = req.header("Authorization"); if (authHeader.empty()) { co_return HttpResponse::badRequest("Missing Authorization header"); } co_return co_await next(req); }); // ============ 路由 ============ // GET / — 首页 server.router().get("/", [](const HttpRequest&) -> HttpResponse { return HttpResponse::ok("User Management API v1.0"); }); // GET /api/status — 状态查询 server.router().get("/api/status", [](const HttpRequest&) -> HttpResponse { return HttpResponse::json({ {"status", "running"}, {"version", "0.2.0"}, {"framework", "hical"} }); }); // GET /api/users — 用户列表 server.router().get("/api/users", [](const HttpRequest& req) -> HttpResponse { // 查询参数示例 auto query = req.query(); json::array users; users.push_back(json::object{ {"id", 1}, {"name", "Alice"}, {"email", "alice@example.com"}}); users.push_back(json::object{ {"id", 2}, {"name", "Bob"}, {"email", "bob@example.com"}}); return HttpResponse::json({{"users", users}, {"total", 2}}); }); // GET /users/{id} — 查询单个用户(路径参数) server.router().get("/users/{id}", [](const HttpRequest& req) -> HttpResponse { auto userId = req.param("id"); return HttpResponse::json({ {"id", userId}, {"name", "User " + userId}, {"email", userId + "@example.com"} }); }); // POST /api/users — 创建用户(JSON 请求体) server.router().post("/api/users", [](const HttpRequest& req) -> HttpResponse { try { auto body = req.jsonBody(); auto& obj = body.as_object(); // 提取字段 auto name = std::string(obj.at("name").as_string()); auto email = std::string(obj.at("email").as_string()); return HttpResponse::json({ {"message", "User created"}, {"name", name}, {"email", email} }); } catch (const std::exception& e) { return HttpResponse::badRequest( std::string("Invalid JSON: ") + e.what()); } }); // PUT /users/{id} — 更新用户 server.router().put("/users/{id}", [](const HttpRequest& req) -> HttpResponse { auto userId = req.param("id"); auto body = req.jsonBody(); return HttpResponse::json({ {"message", "User " + userId + " updated"}, {"data", body} }); }); // DELETE /users/{id} — 删除用户 server.router().del("/users/{id}", [](const HttpRequest& req) -> HttpResponse { auto userId = req.param("id"); return HttpResponse::json({ {"message", "User " + userId + " deleted"} }); }); // ============ 启动 ============ std::cout << "hical User API Server listening on :8080" << std::endl; server.start(); } 1.2 测试 API 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 # 状态查询 curl http://localhost:8080/api/status # 用户列表 curl -H "Authorization: Bearer token" http://localhost:8080/api/users # 查询单个用户 curl -H "Authorization: Bearer token" http://localhost:8080/users/42 # 创建用户 curl -X POST http://localhost:8080/api/users \ -H "Authorization: Bearer token" \ -H "Content-Type: application/json" \ -d '{"name":"Charlie","email":"charlie@example.com"}' # 删除用户 curl -X DELETE -H "Authorization: Bearer token" http://localhost:8080/users/42 2. 完整案例:WebSocket 实时通信 2.1 WebSocket Echo + 广播 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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 #include "core/HttpServer.h" #include "core/WebSocket.h" #include <iostream> #include <mutex> #include <set> using namespace hical; // 简单的连接管理器 struct ConnectionManager { std::mutex mutex; std::set<WebSocketSession*> sessions; void add(WebSocketSession* s) { std::lock_guard lock(mutex); sessions.insert(s); } void remove(WebSocketSession* s) { std::lock_guard lock(mutex); sessions.erase(s); } }; int main() { HttpServer server(8080); ConnectionManager conns; // HTTP 路由 server.router().get("/", [](const HttpRequest&) -> HttpResponse { return HttpResponse::ok("WebSocket Server - connect to /ws/echo or /ws/chat"); }); // WebSocket Echo server.router().ws("/ws/echo", [](const std::string& msg, WebSocketSession& ws) -> Awaitable<void> { co_await ws.send("Echo: " + msg); }, [](WebSocketSession& ws) -> Awaitable<void> { co_await ws.send("Connected to echo service!"); }); // WebSocket Chat(广播) server.router().ws("/ws/chat", // 消息回调:广播给所有连接 [&conns](const std::string& msg, WebSocketSession& ws) -> Awaitable<void> { std::lock_guard lock(conns.mutex); for (auto* session : conns.sessions) { if (session != &ws && session->isOpen()) { co_await session->send(msg); } } }, // 连接回调:注册到管理器 [&conns](WebSocketSession& ws) -> Awaitable<void> { conns.add(&ws); co_await ws.send("Welcome to the chat room!"); }); std::cout << "WebSocket Server on :8080" << std::endl; server.start(); } 3. 反射宏系统 3.1 HICAL_JSON:自动 DTO 序列化 手动写 JSON 序列化代码很繁琐。hical 提供了 HICAL_JSON 宏,一行代码实现结构体到 JSON 的自动转换: ...

April 12, 2026 · 11 min · 2188 words

告别回调地狱:在 C++ Web 框架中全面拥抱协程

告别回调地狱:在 C++ Web 框架中全面拥抱协程 本文以 Hical 框架为例,展示如何用 C++20 协程 + Boost.Asio 构建一个全协程化的 HTTP 服务器,以及这样做的工程权衡。 回调有什么问题? 几乎所有 C++ 网络框架的 1.0 版本都是回调驱动的。一个简单的"读取请求 → 处理 → 发送响应"流程,回调版本长这样: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 void onAccept(tcp::socket socket) { auto buf = std::make_shared<flat_buffer>(); auto req = std::make_shared<http::request<string_body>>(); http::async_read(socket, *buf, *req, [&socket, buf, req](error_code ec, size_t) { if (ec) return; auto res = std::make_shared<http::response<string_body>>(); // ... 处理请求,构建响应 ... http::async_write(socket, *res, [&socket, res](error_code ec, size_t) { if (ec) return; socket.shutdown(tcp::socket::shutdown_send); }); }); } 问题很明显: ...

April 12, 2026 · 3 min · 539 words

用 C++20 Concepts 设计可替换的网络后端:从 Boost.Asio 到未来的 io_uring

用 C++20 Concepts 设计可替换的网络后端:从 Boost.Asio 到未来的 io_uring 本文以 Hical 框架为例,展示如何用 C++20 Concepts 约束网络后端接口,实现编译期类型安全的后端抽象。 问题:网络后端绑定的困境 大多数 C++ 网络框架和底层网络库深度绑定。Drogon 绑定 Trantor,muduo 绑定自研的 EventLoop。一旦想换后端(比如从 epoll 切到 io_uring),基本等于重写。 原因是传统的抽象手段——虚函数继承——有两个问题: 运行时开销:每次调用都经过 vtable 接口松散:基类定义了接口,但"你的实现是否真的完整?“只能在链接期或运行时才知道 Concepts:编译期的接口约束 C++20 Concepts 提供了一种具名约束机制——在编译期验证类型是否满足一组要求: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 template <typename T> concept EventLoopLike = requires(T loop, std::function<void()> func, double delay) { { loop.run() } -> std::same_as<void>; { loop.stop() } -> std::same_as<void>; { loop.isRunning() } -> std::convertible_to<bool>; { loop.post(func) } -> std::same_as<void>; { loop.dispatch(func) } -> std::same_as<void>; { loop.runAfter(delay, func) } -> std::convertible_to<uint64_t>; { loop.runEvery(delay, func) } -> std::convertible_to<uint64_t>; { loop.cancelTimer(uint64_t{}) } -> std::same_as<void>; { loop.isInLoopThread() } -> std::convertible_to<bool>; { loop.index() } -> std::convertible_to<size_t>; { loop.allocator() } -> std::same_as<std::pmr::polymorphic_allocator<std::byte>>; }; 如果某个类型缺少 run() 方法或返回类型不对,编译器立即报错,而不是在链接时给出晦涩的"未定义引用”。 ...

April 12, 2026 · 3 min · 580 words

深入学习 C++20 协程(Coroutines)

深入学习 C++20 协程(Coroutines) 头文件:<coroutine> 命名空间:std 编译器要求:GCC 11+ / Clang 14+ / MSVC 19.28+(均需 -std=c++20 或以上) 注意:GCC 10 / Clang 8~13 可通过 -fcoroutines 和 <experimental/coroutine> 使用实验性支持 一、为什么需要协程? 1.1 异步编程的传统痛点 游戏服务器中充斥着异步操作——数据库查询、网络 I/O、定时器回调。传统方案各有各的痛: 方案 A:回调地狱(Callback Hell) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 void HandleLogin(Connection* conn, const LoginPacket& pkt) { // 第1步:查询数据库验证账号 dbManager->QueryAsync("SELECT * FROM accounts WHERE name=?", pkt.name, [conn, pkt](const DBResult& result) { if (!result.ok) { conn->SendError("DB错误"); return; } // 第2步:查询角色列表 dbManager->QueryAsync("SELECT * FROM characters WHERE account_id=?", result.accountId, [conn](const DBResult& charResult) { if (!charResult.ok) { conn->SendError("DB错误"); return; } // 第3步:加载角色数据 dbManager->QueryAsync("SELECT * FROM inventory WHERE char_id=?", charResult.charId, [conn, charResult](const DBResult& invResult) { // 第4步:终于可以发送登录成功了... conn->SendLoginSuccess(charResult, invResult); }); }); }); } 方案 B:状态机(State Machine) ...

April 8, 2026 · 22 min · 4611 words

深入学习 io_uring(三):C++ 封装、协程集成与高性能架构

系列导航:入门篇 | 进阶篇 | 实战篇 前置知识 已阅读入门篇和进阶篇,掌握 io_uring 双环形缓冲区和 liburing API 了解 C++20 协程基础(co_await、coroutine_handle、promise_type) 建议先阅读 深入学习 Boost.Asio(三):实战篇 中的协程部分作为对照 1. RAII 封装:安全管理 io_uring 资源 1.1 为什么需要 C++ 封装 直接使用 liburing 的 C API 有三个痛点: io_uring_queue_init / io_uring_queue_exit 手动配对,容易遗漏 user_data 是 void* 或 uint64_t,类型安全全靠人肉 提交→收割的事件循环代码高度模板化,每个项目重写一遍 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 封装层次: 应用代码(协程/回调) │ ▼ ┌──────────────────────┐ │ IoUringAwaitable │ ← 协程集成层(co_await 一个 I/O 操作) └──────────────────────┘ │ ▼ ┌──────────────────────┐ │ IoUringContext │ ← 事件循环层(submit / wait / dispatch) └──────────────────────┘ │ ▼ ┌──────────────────────┐ │ IoUring (RAII) │ ← 资源管理层(init / exit) └──────────────────────┘ │ ▼ liburing C API 1.2 IoUring:RAII 包装 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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 // IoUring.hpp — RAII 封装 io_uring 实例 // 编译:g++ -std=c++20 -O2 xxx.cpp -luring -o xxx #pragma once #include <liburing.h> #include <stdexcept> #include <string> #include <cstring> class IoUring { public: // 构造时初始化 io_uring,指定 SQ 大小和可选标志 explicit IoUring(unsigned entries, unsigned flags = 0) { int ret = io_uring_queue_init(entries, &ring_, flags); if (ret < 0) { throw std::runtime_error( "io_uring_queue_init 失败: " + std::string(strerror(-ret))); } } // 禁止拷贝(io_uring 资源不可共享) IoUring(const IoUring&) = delete; IoUring& operator=(const IoUring&) = delete; // 允许移动 IoUring(IoUring&& other) noexcept : ring_(other.ring_) { other.moved_ = true; } // 析构时自动清理 ~IoUring() { if (!moved_) { io_uring_queue_exit(&ring_); } } // 获取 SQE(SQ 满时自动 submit 腾出空间) io_uring_sqe* getSqe() { io_uring_sqe* sqe = io_uring_get_sqe(&ring_); if (!sqe) { // SQ 满了,先提交当前积压的请求 io_uring_submit(&ring_); sqe = io_uring_get_sqe(&ring_); if (!sqe) { throw std::runtime_error("SQ 空间不足,即使 submit 后仍无法获取 SQE"); } } return sqe; } int submit() { return io_uring_submit(&ring_); } // 阻塞等待至少一个 CQE io_uring_cqe* waitCqe() { io_uring_cqe* cqe = nullptr; int ret = io_uring_wait_cqe(&ring_, &cqe); if (ret < 0) { throw std::runtime_error( "io_uring_wait_cqe 失败: " + std::string(strerror(-ret))); } return cqe; } // 非阻塞查看 CQE io_uring_cqe* peekCqe() { io_uring_cqe* cqe = nullptr; int ret = io_uring_peek_cqe(&ring_, &cqe); if (ret == -EAGAIN) return nullptr; // 无就绪 CQE if (ret < 0) { throw std::runtime_error( "io_uring_peek_cqe 失败: " + std::string(strerror(-ret))); } return cqe; } // 标记 CQE 已消费 void seenCqe(io_uring_cqe* cqe) { io_uring_cqe_seen(&ring_, cqe); } // 访问底层 io_uring(高级用法需要) io_uring* raw() { return &ring_; } private: io_uring ring_{}; bool moved_ = false; }; 设计原则:RAII 保证 io_uring_queue_exit 一定被调用,即使异常传播也不会泄漏内核资源。getSqe() 中自动 submit 是防御性编程——避免 SQ 满导致的隐性 bug。 ...

October 3, 2025 · 14 min · 2887 words

深入学习 Boost.Asio(三):协程进阶与实战项目

系列导航:入门篇 | 进阶篇 | 实战篇 前置知识 阅读本篇前,请确保已掌握: 入门篇:io_context、异步操作生命周期、定时器 进阶篇:协程 Echo Server、多线程模型、strand 1. 协程进阶技巧 1.1 co_spawn 的第三个参数 co_spawn 的第三个参数决定了协程完成后的行为: 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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 #include <boost/asio.hpp> #include <boost/asio/co_spawn.hpp> #include <boost/asio/detached.hpp> #include <boost/asio/use_awaitable.hpp> using boost::asio::awaitable; using boost::asio::use_awaitable; awaitable<int> compute() { co_return 42; } awaitable<void> mayFail() { throw std::runtime_error("oops"); co_return; } void examples(boost::asio::io_context& ioCtx) { // 方式1:detached —— 忽略返回值和异常 // 适用:独立运行的协程(如连接处理) boost::asio::co_spawn(ioCtx, compute(), boost::asio::detached); // 方式2:回调 —— 协程完成时执行回调 // 适用:需要捕获协程异常或获取返回值 boost::asio::co_spawn(ioCtx, mayFail(), [](std::exception_ptr e) { if (e) { try { std::rethrow_exception(e); } catch (const std::exception& ex) { std::cerr << "协程异常: " << ex.what() << "\n"; } } }); // 方式3:use_awaitable —— 在协程中等待另一个协程 // 适用:父子协程关系 // (需要在协程内使用) } // 方式3 完整示例 awaitable<void> parent(boost::asio::io_context& ioCtx) { // 等待子协程完成并获取返回值 int result = co_await boost::asio::co_spawn( ioCtx, compute(), boost::asio::use_awaitable); std::cout << "子协程返回: " << result << "\n"; // 42 } 1.2 超时控制 生产环境中,你不能无限等待一个操作完成。Asio 提供了 awaitable_operators 实现竞争式等待: ...

May 21, 2025 · 10 min · 1926 words

IO 与无锁序列:cppcoro 的网络文件 I/O,以及 LMAX Disruptor 的协程化

IO 与无锁序列:cppcoro 的网络文件 I/O,以及 LMAX Disruptor 的协程化 本专栏文章:拆开 cppcoro 给你看 · 第 6 篇(完结篇) 这是系列的最后一篇,也是最"硬"的一篇。要搞懂两件事:① cppcoro 怎么把 Windows IOCP 封装成协程友好的文件/网络 I/O 接口;② cppcoro 借鉴 LMAX Disruptor 的无锁序列怎么协程化,让消费者不是忙等而是挂起等待。 注:IOCP 基础、win32_overlapped_operation CRTP 模式、四态取消状态机、cancellation_token 三层模型、io_service 和 async_scope 在上一篇 Layer 3 中已经讲过了。本篇聚焦文件 I/O 类型层次、socket 的协程封装,以及无锁序列原语的设计推导。 1. 文件 I/O 类型体系 1.1 类层次结构 1 2 3 4 5 6 file (基类: size(), 持有 Windows HANDLE) ├── readable_file (抽象: read(offset, buffer, size)) │ └── read_only_file (具体类: open() 工厂方法) ├── writable_file (抽象: write(offset, buffer, size), set_size()) │ └── write_only_file (具体类: open() 工厂方法) └── read_write_file (多继承: readable_file + writable_file) 1.2 为什么用静态工厂? 1 2 3 4 5 static read_only_file open( io_service& ioService, const path& path, file_share_mode shareMode = file_share_mode::read, file_buffering_mode bufferingMode = file_buffering_mode::default_); Windows 上打开文件涉及多个系统调用(CreateFile + CreateIoCompletionPort),且可能失败。工厂方法把全部设置逻辑封装在一处,返回值语义对象,调用者负责生命周期。 ...

November 30, 2024 · 6 min · 1080 words

协程调度器到底在调度什么?从 inline_scheduler 到工作窃取线程池

协程调度器到底在调度什么?从 inline_scheduler 到工作窃取线程池 本专栏文章:拆开 cppcoro 给你看 · 第 5 篇 前几篇我们一直在协程"内部"兜圈子——task 怎么设计、同步原语怎么写、并发怎么编排。但有一个问题一直没认真回答:协程在哪个线程上执行? co_await 之后你可能在任何线程上恢复——这取决于谁调了 handle.resume()。大部分时候这不是问题,但有时你确实想控制恢复的线程。这就是调度器的概念。 本文分三层递进: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 Layer 1: 调度器概念(30 分钟能读完) ├─ inline_scheduler — 22 行,不调度也是调度 ├─ round_robin_scheduler — 125 行,对称转移调度的杰作 └─ schedule_on vs resume_on Layer 2: 工作窃取线程池(核心) ├─ 本地 LIFO 队列 ├─ 全局 MPSC 队列 ├─ 偷取 (work-stealing) └─ 睡眠/唤醒协议 Layer 3: IOCP + 取消 + 文件/网络 I/O ├─ OVERLAPPED 嵌入 awaiter (CRTP) ├─ 可取消操作的四态状态机 ├─ cancellation_token/source/registration ├─ io_service + io_work_scope └─ async_scope Layer 1:调度器概念 问题:协程在哪个线程上执行? 1 2 3 4 5 6 task<> my_coro() { std::cout << "I'm on thread " << std::this_thread::get_id() << std::endl; co_await some_io(); std::cout << "Now I'm on thread " << std::this_thread::get_id() << std::endl; // ↑ 恢复后可能在完全不同的线程上! } 1.1 inline_scheduler —— “不调度"也是调度 1 2 3 4 5 6 class inline_scheduler { public: std::suspend_never schedule() const noexcept { return {}; // 不挂起,原地继续 } }; 返回 suspend_never 意味着 co_await scheduler.schedule() 等价于什么都不做。但它在泛型代码中有实际意义——你的函数接受一个调度器参数,inline_scheduler 就是"不需要调度的调度器”。 ...

November 25, 2024 · 6 min · 1067 words

从 generator 到 async_generator:协程生成器的三层进化

从 generator 到 async_generator:协程生成器的三层进化 本专栏文章:拆开 cppcoro 给你看 · 第 4 篇 前几篇一直在讲"等一个结果返回"的协程模式——task<T>、同步原语、when_all。但协程还有另一面:用协程产生一系列值,而不是一次返回一个结果。 cppcoro 为此提供了三种生成器,每一种都是为解决前一种的瓶颈而生的。本文从一个具体痛点出发:遍历一棵二叉树的所有节点,看着普通的 generator 怎么在递归场景下性能退化成 O(N²),然后 recursive_generator 怎么用 O(1) 的 pull() 解决,最后 async_generator 怎么让生成器支持 co_await。 1 2 3 generator<T> → O(1) 遍历平面序列,但递归时 operator++() 退化成 O(depth) recursive_generator<T> → pull() 直接驱动叶子,递归场景 O(1) async_generator<T> → 支持 co_await,值可以异步产生 1. generator<T> —— 同步惰性序列 1.1 最简单的使用场景 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 generator<int> fibonacci() { int a = 0, b = 1; for (int i = 0; i < 10; ++i) { co_yield b; int t = a; a = b; b += t; } } int main() { for (int n : fibonacci()) { std::cout << n << " "; // 1 1 2 3 5 8 13 21 34 55 } } 1.2 和 task<T> 的设计对比 特性 task<T> generator<T> 用途 产生一个最终结果 产生一系列中间值 关键字 co_return co_yield 可以用 co_await? ✅ ❌ final_suspend FinalAwaiter(转移控制权) suspend_always 谁决定何时结束 协程自己 (co_return) 调用者 (不再调用 ++it) 1.3 为什么 final_suspend 是 suspend_always? 与 task<T> 的 FinalAwaiter(把控制权转回等待者)不同,generator 的 final_suspend 就是纯纯的 suspend_always。 ...

November 22, 2024 · 6 min · 1256 words

协程世界的并发编排:从需求反推 when_all 的实现

协程世界的并发编排:从需求反推 when_all 的实现 本专栏文章:拆开 cppcoro 给你看 · 第 3 篇 这篇文章我想换个写法。前两篇是"先给答案再解释",这篇反过来——从需求出发,一步步推到最终实现。 需求很简单:三个查询并发执行,全部完成后取结果。但从这个需求到最终的 when_all,踩了三个坑:顺序等待太慢 → 手动管理太繁琐 → 注册竞态太难搞。我们逐个填。 1. 场景引入:三个并发查询 1 2 3 task<User> load_user(int id); task<Order> load_orders(int userId); task<Address> load_address(int userId); 尝试 1:顺序等待(慢) 1 2 3 4 5 6 task<void> handle(int userId) { auto user = co_await load_user(userId); // ~200ms auto orders = co_await load_orders(user.id); // ~300ms auto address = co_await load_address(user.id); // ~100ms // 总耗时: ~600ms——三个没有依赖的操作却串行执行了 } 尝试 2:手动管理 counter(繁琐) 每次需要并发时都要手动写计数器逻辑——大量重复代码,容易出错。 我们需要一个泛化的并发执行工具。这就是 when_all 的由来。 2. 核心问题:怎么知道"所有任务都完成了"? 答案就是上一篇文章提到的 when_all_counter: 1 2 3 4 class when_all_counter { std::atomic<std::size_t> m_count; std::coroutine_handle<> m_awaitingCoroutine; }; 让我们从头推导它的设计。 Step 1:计数器初始化 1 when_all_counter counter(3); // 3 个任务 Step 2:每个任务完成时递减 1 2 3 4 5 6 void notify_awaitable_completed() noexcept { if (m_count.fetch_sub(1, std::memory_order_acq_rel) == 1) { // 我是最后一个完成的 → 唤醒等待者 m_awaitingCoroutine.resume(); } } Step 3:等待者注册自己 1 2 3 4 5 6 bool try_await(std::coroutine_handle<> awaitingCoroutine) noexcept { m_awaitingCoroutine = awaitingCoroutine; return m_count.fetch_sub(1, std::memory_order_acq_rel) > 1; // >1 → 还有任务没完成 → 挂起 // ==1 → 所有任务在注册前就完成了 → 不挂起 } 为什么 m_count 初始 = 任务数 + 1? 💡 这是本文第一个关键洞察——额外的那 1 票代表"注册尚未完成"。 ...

November 18, 2024 · 7 min · 1283 words