Hical 性能优化全记录

优化背景 Hical 是我写的 C++20/26 Web 框架,跑 Hello World 压测时起初只有 27K QPS,而同类框架(Cinatra 165K、Drogon 170K)差了将近一个数量级。目标很明确:追平 Cinatra/Drogon 的水平。 整个优化过程分 6 个阶段,不是拍脑袋乱改,每一步都是 perf + 火焰图定位瓶颈 → 想方案 → 写代码 → 跑压测验证 的循环。能看到数字变化才算数。 阶段 1:协程帧削减(v2.5.1-v2.5.2) 发现问题 perf 火焰图第一个大头:14.5% CPU 在 scheduler::wake_one_thread_and_unlock + pthread_cond_signal。 一开始以为是跨线程调度问题,仔细一看不是——是 Boost.Asio scheduler 每次 co_await resume 都要走的内部调度流程太重了。一个 Hello World 请求居然走了 4 个协程帧: 1 2 3 4 5 handleSession: co_await async_read → 帧 1(必需,I/O 等待) co_await router_.dispatch() → 帧 2(Router 本身是协程) co_await handler(req) → 帧 3(同步 handler 被包装成协程,不必要!) co_await async_write → 帧 4(必需,I/O 等待) 帧 1 和 4 是真正的 I/O 等待不可消除,但帧 2 和 3 完全是浪费——一个同步的 return HttpResponse("Hello") 被裹了两层协程。 ...

May 22, 2026 · 9 min · 1794 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, 2026 · 10 min · 1926 words

深入学习 Boost.Asio(二):TCP 编程与多线程模型

系列导航:入门篇 | 进阶篇 | 实战篇 前置知识 阅读本篇前,请确保已理解 入门篇 中的以下概念: io_context 的作用和 run() 执行流程 异步操作的生命周期(发起 → 完成 → handler 执行) post/dispatch 的区别 1. TCP 编程:三步演进 我们通过构建一个 Echo Server(收到什么就回什么),从最简单的同步版本逐步演进到生产级协程版本。 1.1 第一步:同步阻塞版 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 // echo_server_sync.cpp // 编译: g++ -std=c++20 echo_server_sync.cpp -lboost_system -lpthread -o echo // 测试: 另开终端 nc localhost 9999,输入文字会回显 #include <boost/asio.hpp> #include <iostream> using boost::asio::ip::tcp; int main() { boost::asio::io_context ioCtx; // 创建 acceptor:监听 TCP 连接 // 参数:io_context, 绑定地址(IPv4, 端口9999) tcp::acceptor acceptor(ioCtx, tcp::endpoint(tcp::v4(), 9999)); std::cout << "同步 Echo Server 监听端口 9999\n"; while (true) { // accept() 阻塞,直到有客户端连接 tcp::socket socket(ioCtx); acceptor.accept(socket); std::cout << "客户端连接: " << socket.remote_endpoint().address().to_string() << ":" << socket.remote_endpoint().port() << "\n"; // 处理这个连接(阻塞:处理期间无法接受新连接!) boost::system::error_code ec; char buf[1024]; while (true) { // read_some:读取可用的数据(可能只有一部分) size_t n = socket.read_some(boost::asio::buffer(buf), ec); if (ec == boost::asio::error::eof) { std::cout << "客户端断开\n"; break; } if (ec) throw boost::system::system_error(ec); // 将收到的数据原样写回 boost::asio::write(socket, boost::asio::buffer(buf, n)); } } return 0; } 问题:同一时刻只能服务一个客户端。当客户端 A 连接后,客户端 B 必须等 A 断开才能被接受。 ...

May 20, 2026 · 10 min · 2026 words

Hical 踩坑实录五部曲(五):Boost.MySQL 协程集成的 5 个坑

Hical 踩坑实录五部曲(五):Boost.MySQL 协程集成的 5 个坑 引言 Hical 的数据库模块(src/db/)是一个基于协程的连接池 + 中间件系统,后端使用 Boost.MySQL 的 any_connection。从 “能跑” 到 “能在生产环境跑”,中间踩了不少坑。 这篇文章记录了 Boost.MySQL 协程集成过程中遇到的 5 个真实问题,每个都附带完整的解决方案代码。 目录 Hical 踩坑实录五部曲(五):Boost.MySQL 协程集成的 5 个坑 引言 目录 坑 1:any_connection vs 强类型连接的取舍 坑 2:PreparedStatement 失效与自动重试 坑 3:SET NAMES 注入风险——validateCharset 白名单 坑 4:连接池 acquire 超时的竞争窗口 坑 5:事务忘记 commit/rollback 的自动回滚设计 第一道防线:DbMiddleware 洋葱模型 第二道防线:连接池 release 兜底回滚 附:StmtCache LRU 缓存设计 总结:Boost.MySQL 集成检查清单 坑 1:any_connection vs 强类型连接的取舍 现象:第一版连接池用 Boost.MySQL 的强类型连接(tcp_ssl_connection),结果泛型代码全部被迫模板化——编译时间爆炸,且无法在运行时根据配置切换 TCP/SSL。 强类型方式的问题: 1 2 3 4 5 6 7 8 9 10 11 // ❌ 强类型——泛型代码必须模板化 template <typename Connection> class DbPool { std::vector<std::unique_ptr<Connection>> idle_; // Connection 是 tcp_connection 还是 tcp_ssl_connection? // 中间件也要模板化、查询日志也要模板化... }; // 编译时决定,运行时无法切换 using Pool = DbPool<boost::mysql::tcp_ssl_connection>; 解决方案:any_connection——类型擦除,运行时决定传输层: ...

May 11, 2026 · 8 min · 1605 words

Hical 踩坑实录五部曲(一):Boost.Asio 协程开发的 N 个坑

Hical 踩坑实录五部曲(一):Boost.Asio 协程开发的 N 个坑 引言 Hical 的所有异步 I/O 都基于 Boost.Asio 协程(co_await + boost::asio::use_awaitable)。路由处理器返回 Awaitable<HttpResponse>,中间件用洋葱模型 co_await next(req),连接池用 co_await timer.async_wait() 做非阻塞等待。 协程消除了回调地狱,但引入了一套全新的陷阱。这篇记录的每一个坑,都是在压测或线上环境中真实触发过的。 目录 Hical 踩坑实录五部曲(一):Boost.Asio 协程开发的 N 个坑 引言 目录 坑 1:co_await 后 this 悬挂——对象已析构 坑 2:协程异常传播——catch 里不能 co_await 坑 3:steady_timer 当协程信号量的技巧 坑 4:jthread vs thread——精准匹配停止信号 坑 5:多线程 io_context + 协程的线程安全陷阱 坑 6:detached 协程的异常黑洞 坑 7:io_context::stop() 不等于安全退出 总结:协程安全编程检查清单 坑 1:co_await 后 this 悬挂——对象已析构 现象:压测时低概率崩溃,堆栈指向 TcpServer 的 accept 循环,访问了已释放的内存。 最小复现: 1 2 3 4 5 6 7 8 9 10 11 // ❌ 危险的写法 Awaitable<void> TcpServer::acceptLoop() { while (running_) { auto socket = co_await acceptor_.async_accept(use_awaitable); // ⚠️ 如果在 co_await 期间 TcpServer 被析构, // this 已经是悬空指针! this->createConnection(std::move(socket)); // 💥 use-after-free } } 根因:协程帧通过 co_spawn(io_context, coroutine, detached) 提交到 io_context。协程帧的生命周期由 io_context 管理,与创建协程的对象完全分离。 ...

May 7, 2026 · 8 min · 1586 words

Hical 协程入门:告别回调地狱,用 co_await 写异步 C++

Hical 协程入门:告别回调地狱,用 co_await 写异步 C++ 传统 C++ 异步编程离不开回调嵌套、状态机、手动生命周期管理——代码写得像意大利面。C++20 协程从根本上改变了这一切:异步代码写起来和同步一样直观,编译器帮你管理暂停与恢复。本文从零讲解如何在 Hical 框架中使用协程,不需要你懂 Boost.Asio 底层。 什么是协程?30 秒版本 传统回调式: 1 2 3 4 5 6 7 8 9 10 11 12 // 回调嵌套——"回调地狱" socket.async_read(buffer, [&](error_code ec, size_t n) { if (!ec) { socket.async_write(buffer, [&](error_code ec2, size_t) { if (!ec2) { socket.async_read(buffer, [&](error_code ec3, size_t) { // 继续嵌套... }); } }); } }); 协程式: 1 2 3 4 // 同样的逻辑,协程版——像写同步代码一样 auto n = co_await socket.async_read(buffer, use_awaitable); co_await socket.async_write(buffer, use_awaitable); auto n2 = co_await socket.async_read(buffer, use_awaitable); co_await 会暂停当前函数,等 I/O 完成后自动恢复执行。没有回调,没有嵌套,错误用 try/catch 处理。 Hical 对协程做了什么封装? Hical 在 Coroutine.h 中提供了三个核心工具: ...

May 5, 2026 · 4 min · 762 words

用 Hical + MySQL 5 分钟搭建 CRUD API(C++20 协程版)

用 Hical + MySQL 5 分钟搭建 CRUD API(C++20 协程版) C++ 访问数据库难吗?2026 年不再难了。本文用 Hical 的协程 DB 中间件,带你从零搭建一个完整的 MySQL CRUD API —— 连接池管理、事务自动提交/回滚、慢查询检测,全部开箱即用,代码比大多数 Python 教程还简洁。 三种姿势对比 方式 代码量 连接池 异步 防注入 裸 mysql_query 多,手动管理连接 手写 阻塞 手拼字符串,危险 ORM(如 ODB) 少,但有运行时膨胀 内置 视实现而定 安全 Hical 协程中间件 少,原生协程 内置 非阻塞 co_await PreparedStatement Hical 走第三条路:连接池是协程化的,查询全部走 PreparedStatement 防注入,事务在中间件层自动管理,业务代码只关心 SQL 逻辑。 环境准备 构建启用数据库支持 1 2 3 4 5 6 7 8 9 10 11 12 # Linux / macOS cmake -B build -DHICAL_WITH_DATABASE=ON -DCMAKE_BUILD_TYPE=Release cmake --build build -j$(nproc) # Windows (MSYS2 MINGW64) cmake -B build -G Ninja -DHICAL_WITH_DATABASE=ON -DCMAKE_BUILD_TYPE=Release cmake --build build # Windows (MSVC + vcpkg) cmake -B build -DHICAL_WITH_DATABASE=ON -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake cmake --build build --config Release 依赖说明 Boost >= 1.85(DB 中间件需要 Boost.MySQL,1.85 版引入 any_connection) OpenSSL(MySQL TLS 连接可选,已是框架强依赖) CMakeLists 里加一行即可: 1 2 target_link_libraries(my_app PRIVATE hical::hical_core) # HICAL_WITH_DATABASE=ON 时 hical_core 自动链接 Boost.MySQL 建表 SQL 1 2 3 4 5 6 7 8 9 10 11 CREATE DATABASE IF NOT EXISTS demo CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE demo; CREATE TABLE IF NOT EXISTS users ( id INT UNSIGNED NOT NULL AUTO_INCREMENT, name VARCHAR(64) NOT NULL, email VARCHAR(128) NOT NULL UNIQUE, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 完整 main.cpp 约 80 行,包含连接池初始化、中间件注册、4 个 CRUD 路由: ...

May 1, 2026 · 6 min · 1072 words

从零构建现代C++ Web服务器(六):数据库中间件与协程连接池

从零构建现代C++ Web服务器(六):数据库中间件与协程连接池 系列导航:第一篇:设计理念 | 第二篇:协程与内存池 | 第三篇:路由、中间件与SSL | 第四篇:实战与性能 | 第五篇:Cookie、Session与文件服务 | 第六篇:数据库中间件(本文) 前置知识 阅读过本系列前五篇(特别是第二篇的协程基础和第三篇的中间件洋葱模型) 了解 SQL 基础和 MySQL 数据库操作 了解连接池的基本概念 目录 1. Web 框架为什么需要数据库层 2. 架构总览:六层洋葱 3. 后端抽象:DbConnection 接口 4. MySQL 实现:any_connection 封装 5. LRU PreparedStatement 缓存 6. 协程连接池:用 steady_timer 做信号量 7. DB 中间件:请求级连接生命周期 8. 查询日志:装饰器模式的妙用 9. 综合实战:用户管理 API + 数据库 10. 总结 1. Web 框架为什么需要数据库层 前五篇构建了 hical 的完整 HTTP 骨架——协程驱动的异步 I/O、PMR 内存池、路由、中间件、SSL、Cookie/Session、静态文件。但现实中的 Web 服务几乎都绑定数据库:用户注册要写库、商品查询要读库、交易扣款要事务。 如果把数据库操作留给业务代码自行处理,会出现几个典型问题: 问题 后果 每个请求都新建连接 MySQL 握手 + 认证 ≈ 1-3ms,高并发下成为瓶颈 业务代码管理连接生命周期 忘记关闭 → 连接泄漏,异常时忘记回滚 → 数据不一致 手动拼接 SQL SQL 注入漏洞(游戏服务器的经济系统被注入 = 灾难) 同步 MySQL 客户端 mysql_query() 阻塞 io_context 线程 → 吞吐暴跌 hical v2.3.0 补齐了这最后一块拼图: ...

April 30, 2026 · 20 min · 4080 words

Boost.MySQL 学习课程:异步数据库访问

课程导航:学习路径 | Boost.System | Boost.Asio | Boost.Beast | Boost.JSON | Boost.MySQL 前置知识 课程 1: Boost.System(error_code、system_error) 课程 2: Boost.Asio(io_context、协程、co_await + use_awaitable) SQL 基础(SELECT/INSERT/UPDATE/DELETE、事务) MySQL 数据库基本操作 学习目标 完成本课程后,你将能够: 理解 Boost.MySQL 的类型擦除连接模型(any_connection) 使用 C++20 协程执行异步数据库操作 掌握参数化查询和 PreparedStatement 防 SQL 注入 理解结果集类型体系(results / static_results) 实现事务控制(BEGIN/COMMIT/ROLLBACK) 读懂 Hical 的连接池、Statement 缓存和数据库中间件设计 目录 前置知识 学习目标 目录 1. 核心概念 1.1 Boost.MySQL 的定位 1.2 连接类型体系 1.3 查询执行模型 1.4 结果集类型体系 2. 基础用法 2.1 建立连接 2.2 执行文本查询 2.3 参数化查询(客户端格式化) 2.4 PreparedStatement 2.5 结果集遍历 2.6 事务控制 3. 进阶主题 3.1 类型擦除连接 any_connection 3.2 静态类型结果集 static_results 3.3 多结果集(存储过程) 3.4 连接池 connection_pool 3.5 错误处理与诊断 4. Hical 实战解读 4.1 MysqlConnection:any_connection 的框架封装 4.2 StmtCache:LRU PreparedStatement 缓存 4.3 DbConnectionPool:协程式连接池 4.4 DbMiddleware:请求级连接生命周期 4.5 DbQueryLog:查询日志装饰器 4.6 完整请求处理流程 5. 练习题 练习 1:协程式 CRUD 练习 2:参数化查询实战 练习 3:事务与错误处理 练习 4:LRU 缓存设计 练习 5(挑战):连接池实现 6. 总结与拓展阅读 核心 API 速查表 查询方式对比 拓展阅读 课程回顾 1. 核心概念 1.1 Boost.MySQL 的定位 Boost.MySQL 是一个纯异步的 MySQL 客户端库,直接实现 MySQL 客户端/服务器协议(不依赖 libmysqlclient),天然集成 Boost.Asio 的异步模型。 ...

April 29, 2026 · 38 min · 7911 words

C++20 实战心得:现代 C++ 真正成熟的一代

C++20 实战心得:现代 C++ 真正成熟的一代 C++11 是革命,C++17 是打磨,C++20 是让 C++ 终于像一门「现代语言」。 写在前面 如果说 C++17 的升级是务实的,那 C++20 就是一次结构性的飞跃。协程、Concepts、Ranges、Modules——每一个都是重量级特性。但老实说,截至 2026 年,并非所有特性都已经在生产环境中稳定好用。 这篇文章从我在游戏服务器和 Hical 框架开发中的实际使用出发,聊聊哪些 C++20 特性已经值得用、哪些还需要等等。 一、Concepts —— 模板错误信息终于能看懂了 1.1 C++20 之前的模板报错 先感受一下 C++17 时代的"恐怖": 1 2 std::list<int> lst; std::sort(lst.begin(), lst.end()); GCC 会喷出几十行模板展开错误,核心意思是 std::list::iterator 不是随机访问迭代器——但你得从一堆 __normal_iterator、__gnu_cxx 嵌套模板中自己悟出来。 1.2 Concepts:把约束说人话 1 2 3 4 5 6 7 8 9 template <std::random_access_iterator Iter> void mySort(Iter first, Iter last) { // ... } std::list<int> lst; mySort(lst.begin(), lst.end()); // 错误信息:约束 'random_access_iterator' 不满足 // 一行,清清楚楚 Concepts 的本质:给模板参数加上编译期的「类型契约」。SFINAE 能做的它都能做,但写法是人能读懂的。 ...

April 20, 2026 · 8 min · 1683 words