
为什么不能直接用 std::coroutine_handle 拼接数据库 I/O
协程本身不处理系统调用,read()、write()、connect() 这些仍是阻塞的。你用 co_await 包一层普通 socket 读写,只是把线程卡在协程里,没真正释放线程——结果是并发数上不去,还多了一层调度开销。
真正可行的路径只有一条:让数据库驱动跑在异步 I/O 模型之上,比如 Linux 的 io_uring 或跨平台的 libuv/boost.asio,再把完成事件桥接到协程恢复点。
- 别自己封装
epoll+ 协程调度器,除非你有长期维护 runtime 的人力 - PostgreSQL 官方
libpq不支持异步非阻塞模式(PQsetnonblocking只控制连接建立,后续PQgetResult仍会阻塞) - MySQL Connector/C++ 8.0+ 提供了基于
boost.asio的mysql_async_result,但默认不启用,需手动开启MYSQL_OPT_NONBLOCK并配合async_wait
用 boost.asio 实现 pgsql 异步查询的最小闭环
PostgreSQL 没原生 C++ 异步驱动,但可以用 libpq 的异步函数族(PQconnectStartParams、PQsendQuery、PQgetResult 配合 PQisBusy + PQconsumeInput)配合 asio 的 async_wait on fd 来轮询就绪状态。这不是零拷贝,但能避免线程阻塞。
关键不是“怎么写协程”,而是“怎么把 libpq 的 fd 纳入 asio 的事件循环”:
立即学习“C++免费学习笔记(深入)”;
- 用
PQsocket(conn)拿到 socket fd,包装成asio::posix::stream_descriptor - 不要直接
co_await descriptor.async_wait(...),因为 libpq 内部状态机依赖你主动调用PQconsumeInput和PQisBusy - 正确顺序是:
PQsendQuery→async_waitforasio::posix::stream_descriptor::wait_read→ 在回调里调用PQconsumeInput→ 判断PQisBusy→ 若为 false,则PQgetResult解析结果 - 错误时
PQerrorMessage(conn)才有效,PQresultErrorMessage对未完成结果返回空指针
std::jthread + co_await 混用会踩什么坑
有人想用 std::jthread 启一个后台线程跑阻塞式 libpq 查询,再用 co_await 等待线程结束——这看似“异步”,实则浪费线程资源,且无法取消正在执行的查询(PQcancel 需要单独连接,且不是立即生效)。
-
std::jthread的join()无法被co_await,必须自己实现awaiter,但线程结束和数据库语义无关 - 一旦连接断开或超时,阻塞线程卡在
PQgetResult,std::jthread::request_stop()不影响系统调用 - 更糟的是:多个协程共用一个连接句柄(
PQconn)时,PQsendQuery不是线程安全的,必须加锁,反而串行化了
要不要自己写 coroutine-aware database pool
别急着写。连接池的“异步获取连接”不等于“异步执行 SQL”。多数场景下,瓶颈不在连接建立,而在网络往返和数据库处理延迟。先确认你的 SELECT COUNT(*) FROM huge_table 是卡在 recv() 还是卡在 PostgreSQL 的 executor 阶段。
- 如果使用
io_uring,可用IORING_OP_CONNECT+IORING_OP_SEND/RECV做连接与查询一体化提交,但 PostgreSQL 协议是文本协议,需要自己解析ReadyForQuery、DataRow等消息,工作量远超重用libpq - 连接池本身应返回
std::shared_ptr<connection></connection>,但这个 connection 必须是可 awaitable 的(即内部持有 asio::stream 或 io_uring_sqe_ref),而不是裸指针 - 最易忽略的一点:事务生命周期必须和协程作用域对齐。用
co_await conn.begin()开启事务后,若协程中途被销毁(比如超时取消),必须确保conn.rollback()被调用——这意味着 connection 类得有~connection()或stop_source关联清理逻辑
协程不是银弹,异步数据库最难的部分从来不是语法糖,而是状态同步:连接就绪、查询发送、结果接收、错误传播、取消响应——每个环节都可能落在不同线程、不同 event loop 迭代、甚至不同内核 completion queue 上。写的时候少想“怎么 co_await”,多盯住 PQisBusy 返回 true 时你在做什么。











