Linux下基于io_uring的C++20协程库:提供 task / generator 等基础协程抽象,以及调度器scheduler。支持常用的异步I/O awaitable(read/write/accept/accept_multishot/connect/sleep/yield 等)。代码以头文件接口为主:位于 include/tinycoro/*。
- C++20 协程抽象
tinycoro::task<R>:惰性执行(需要start()或被co_await驱动)、获取返回值、异常传播。tinycoro::generator<T, X, R>:支持co_yield迭代,使用send传值(Python like),并在结束后获取返回值。
- io_uring 调度器
tinycoro::scheduler:schedule(task)以及process_events()处理事件。- 提供
yield()、sleep(duration)等 awaitable。
- 常用 I/O awaitable:见 async_operations.hpp。
- Promise 自定义分配器:支持通过
std::allocator_arg_t注入自定义 allocator 来分配协程帧,便于对大量短生命周期协程进行内存池/PMR 优化。 - 可选 libcurl 集成:检测到
libcurl时编译curl_manager及相关测试代码。
- Linux(依赖 io_uring,内核版本需要大于5.10)
- CMake >= 3.10
- C++20 编译器
pkg-config- 必需依赖:
liburing(开发包) - 可选依赖:
libcurl(开发包;用于curl_manager、curl 测试) - 可选依赖:
asio(开发包;用于 asio 相关基准测试)
# Ubuntu
sudo apt-get update
sudo apt-get install -y cmake g++ pkg-config liburing-dev
# libcurl
sudo apt-get install -y libcurl4-openssl-dev
# asio
sudo apt-get install -y libasio-dev提供了 CMake presets(见 CMakePresets.json)和 VSCode settings(见 .vscode/*.template.json),也支持普通 CMake 命令。
cmake --preset native-release
cmake --build build/native-release -jDebug(带 ASAN/UBSAN/coverage):
cmake --preset native-debug
cmake --build build/native-debug -jcmake -S . -B build/release \
-DCMAKE_BUILD_TYPE=Release \
-DTINYCORO_BUILD_TESTS=ON \
-DTINYCORO_BUILD_BENCHMARKS=ON
cmake --build build/release -j构建完成后:
ctest --test-dir build/native-release --output-on-failure
# 或者
./build/native-release/tests/tinycoro-tests如果检测到 libcurl,会额外启用 test_curl.cpp 里的集成测试。
./build/native-release/benchmarks/tinycoro-bench --benchmark_filter=".*"测试环境为Ubuntu24.04,16核 4602MHz x64 CPU
测试空操作(nop)的调度性能。
- Normal:15.1M/s
- SQPoll:24.0M/s
测试TCP Ping Server的性能。
使用redis-benchmark -t ping -P 1 -c 500 -n 100000 -q测试,约 156.2k req/s,p50 ≈ 1.671 ms
分别测试基于asio(epoll) / asio(io_uring) / tinycoro(io_uring) / tinycoro(io_uring, SQPoll)的PingPong Server(见bm_tcp_server.cpp),结果如下:
固定连接数为500、总请求数为100000(连接池模式),调整accept线程数量和客户端数量,测试结果如下:
固定连接数为500、总请求数为100000、线程数为8,调整每次请求包含的消息数量(pipeline),测试结果如下:
#include <tinycoro/task.hpp>
tinycoro::task<int> compute() {
co_return 42;
}
int main() {
auto t = compute();
t.start();
return t.result();
}#include <tinycoro/scheduler.hpp>
#include <tinycoro/task.hpp>
tinycoro::task<> pingpong(tinycoro::scheduler& sched, int& counter) {
for (int i = 0; i < 100; ++i) {
++counter;
co_await sched.yield();
}
}
int main() {
tinycoro::scheduler sched(256);
int a = 0, b = 0;
sched.schedule(pingpong(sched, a));
sched.schedule(pingpong(sched, b));
sched.process_events();
}#include <tinycoro/generator.hpp>
tinycoro::generator<int> fibonacci() {
int a = 0, b = 1;
while (true) {
co_yield a;
int next = a + b;
a = b;
b = next;
}
}
int main() {
int i = 0;
for (int v : fibonacci()) {
printf("%d\n", v);
if (++i == 10) break;
}
}接口位于:include/tinycoro/async_operations.hpp。
tinycoro::file read_fd, write_fd;
make_pipe(read_fd, write_fd);
tinycoro::scheduler sched(256);
auto expected_data = "Hello, tinycoro!"sv;
auto read_task = [&]() -> tinycoro::task<> {
tinycoro::file readfd = std::move(read_fd);
size_t offset = 0;
std::vector<char> buffer(expected_data.size());
while (offset < expected_data.size()) {
int res = co_await ops::read(
sched, readfd, std::span<char>(buffer.data() + offset, buffer.size() - offset));
offset += res;
}
int res = co_await ops::read(
sched, readfd, std::span<char>(buffer.data(), 1));
EXPECT_EQ(res, 0);
EXPECT_EQ(std::string_view(buffer.data(), buffer.size()), expected_data);
};
auto write_task = [&]() -> tinycoro::task<> {
tinycoro::file writefd = std::move(write_fd);
for (auto c : expected_data) {
int res = co_await ops::write(sched, writefd, std::span<char>(&c, 1));
EXPECT_EQ(res, 1);
}
};
sched.schedule(read_task());
sched.schedule(write_task());
sched.process_events();完整例子见测试文件:test_scheduler.cpp。
#include <tinycoro/curl_manager.hpp>
#include <tinycoro/task.hpp>
namespace curl = tinycoro::curl;
tinycoro::task<> fetch(tinycoro::scheduler& sched) {
curl::manager manager(sched, 10);
curl::handle_t handle;
curl_easy_setopt(handle, CURLOPT_URL, "http://www.example.com/");
curl_easy_setopt(handle, CURLOPT_FOLLOWLOCATION, 1L);
CURLcode ret = co_await manager.perform(handle);
// use the handle
}对应测试在:test_curl.cpp。
tinycoro 的 task 与 generator 都在其 promise_type 上继承了 tinycoro::promise_allocator,用于自定义协程帧的分配与释放。
协程在创建时会分配一块“协程帧”(包含 promise、局部变量、await 状态等)。在高并发/大量短协程场景下,这部分分配可能成为热点。promise_allocator 提供了 operator new/delete:
- 默认路径:未传 allocator 时,选择普通
::operator new。 - 注入 allocator:当协程函数参数列表匹配
std::allocator_arg_t, Alloc, ...时,编译器会优先选择对应的operator new重载,从而使用自定义 allocator 分配协程帧。
下面的示例用 std::pmr 的内存资源给协程帧做池化(也可以使用任意符合标准的 allocator):
#include <memory_resource>
#include <tinycoro/task.hpp>
tinycoro::task<int> compute(
std::allocator_arg_t,
std::pmr::polymorphic_allocator<>,
int x)
{
co_return x + 1;
}
int main() {
std::pmr::monotonic_buffer_resource pool;
std::pmr::polymorphic_allocator<> alloc(&pool);
{
// by function
auto t = compute(std::allocator_arg, alloc, 41);
t.start();
assert(t.result() == 42);
}
{
// by lambda
auto work = [n = 1](std::allocator_arg_t, std::pmr::polymorphic_allocator<>) -> tinycoro::task<int> {
co_return n;
};
auto t = work(std::allocator_arg, alloc);
t.start();
assert(t.result() == 1);
}
}- allocator 相关对象(例如
std::pmr::memory_resource)的生命周期需要长于协程对象的生命周期(例如generator/task的生命周期,直到对应的 coroutine handle 被destroy())为止。 - 该机制只影响协程帧的分配,不会自动替换协程体内对容器/缓冲区的分配;后者仍需显式使用 PMR 容器或自定义 allocator。
TINYCORO_BUILD_TESTS:构建单测(默认 ON)TINYCORO_BUILD_BENCHMARKS:构建 benchmarks(默认 OFF)TINYCORO_CURRENT_LOGLEVEL:设置日志级别TINYCORO_ENABLE_ASAN/TINYCORO_ENABLE_UBSAN/TINYCORO_ENABLE_COVERAGE

