Browse by type
简中 | English
co_context 是一个协程并发框架,提供可靠的性能和强易用性,让 C++ 初学者也能轻松写出高并发程序。 co_context 基于 Linux io_uring,其性能通常优于 epoll。
不了解协程?
我在 Bilibili 上传了协程系列视频,请多指教!
read write accept timeout 等 io_uring 提供的所有系统调用,总计 74 个功能。any, some, all, mutex, semaphore, condition_variable, channel。yield, resume_on。timeout, timeout_at, stop_token。uname -r 即可查看你的内核版本。cmake -B build && cmake --build build -j
build/example/timer # 跑一个小示例
创建一个 io_context,用于运行协程:
using namespace co_context;
io_context context;
使用 task<> 定义一个 socket 监听协程。task<> 就像一个普通的 void 函数,但可以在里面使用 co_await。
task<> server(uint16_t port) {
acceptor ac{inet_address{port}};
for (int sockfd; (sockfd = co_await ac.accept()) >= 0;) {// 异步接受 client
co_spawn(session(co_context::socket{sockfd})); // 每个连接生成一个 worker 任务
}
}
继续使用 task<T> 描述业务逻辑,例如读取 socket 的内容并输出到 stdout:
task<> session(co_context::socket sock) {
char buf[8192];
int nr = co_await sock.recv(buf);
// 不断接收字节流并打印到stdout
while (nr > 0) {
co_await lazy::write(STDOUT_FILENO, {buf, (size_t)nr});
nr = co_await sock.recv(buf);
}
}
task<T> 和普通 T 函数一样可以任意嵌套:
task<int> the_answer() {
co_return 42;
}
task<> universe() {
printf("The answer is %d\n", co_await the_answer());
co_return;
}
如何写一个 main():
int main(int argc, const char *argv[]) {
if (argc < 3) {
printf("Usage:\n %s hostname port\n %s -l port\n", argv[0], argv[0]);
return 0;
}
io_context context; // 1. 定义一个 io_context
int port = atoi(argv[2]);
if (strcmp(argv[1], "-l") == 0) {
context.co_spawn(server(port)); // 2. 至少创建一个 task<>
} else {
context.co_spawn(client(argv[1], port));
}
context.start(); // 3. 启动 io_context 线程
context.join(); // 4. 需要时等待 io_context 线程
return 0;
}
task<> my_clock() {
for (int cnt = 0;;) {
printf("Time = %d\n", cnt++);
co_await timeout(1s);
}
}
秒懂的超时控制。
task<> session(co_context::socket peer) {
char buf[8192];
int nr = co_await timeout(peer.recv(buf), 3s); // 限时3秒
while (nr > 0) {
co_await lazy::write(STDOUT_FILENO, {buf, (size_t)nr});
nr = co_await timeout(peer.recv(buf), 3s); // 限时3秒
}
log_error(-nr);
}
void log_error(int err) {
switch (err) {
case ECANCELED:
log::e("timeout!\n");
break;
default:
log::e("%s\n", strerror(err));
break;
}
}
每个 io_context 代表一个线程。要负载均衡,只需将 task<> 分配到合理的 io_context。
借助 resume_on(),可以在运行时任意切换线程。
同步执行的生成器,可配合std::range。 需要 g++
co_context::generator<int> gen_iota(int x) {
while (true)
co_yield x++;
}
int main() {
using namespace std::views;
for (int x : gen_iota(1) | drop(5) | take(3)) {
std::cout << x << " ";
}
// 输出 6 7 8
return 0;
}
用 && 来链接两个 I/O。链接 I/O 可以减少重入内核态和调度器,增强性能表现。(默认只返回最后一个返回值。)
nr = co_await (
peer.send({buf, (size_t)nr})
&& peer.recv(buf)
);
此例子利用 link_io 大幅增强 echo_server 的性能
借鉴自 Go 语言的阻塞队列。
Draft
co_context 在开发过程中表现出惊人的性能。早期测试见我的博客。下一个开发周期将进行更多测试。
由于内置动态内存分配,基于协程的异步框架可能不是性能的最优解,如果你正处于类似 30ns 延迟的极端性能场景,且不在乎编程复杂度,推荐关注 sender/receiver model,而无需尝试协程。
如果你希望异步框架能够最佳地平衡「开发、维护成本」和「项目质量、性能」,从而最大化经济效益,推荐你关注协程方案。感性理解:协程 + 内核态 I/O 的性能类似于 Redis 的网络模块。
co_context 竭尽所能避免缓存问题:
operator new 引起),需要借助其他工具来解决,例如 mimalloc。operator new 来分配 frame:&& ||。std::execution 能否兼容一种激进的 IO awaiter,在构造函数中初始化 IO 请求并提交。
在被 co_await 时,若 IO 早已完成,则无需让出。否则,需要等待 IO 完成后由调度器唤醒。
涉及多线程并行,需要同步 IO 的状态(未完成、已完成)。至少要保证:调度器必须确保 「eager_io 已经知悉 IO 已完成」,否则可能丢数据。
TODO: 改用原子变量,弃用检查队列
co_context 假设大多数 eager_io 会陷入「等待状态」,以此为优化立足点
co_await,检查结果:xxx <-> is_detached is_waiting is_ready
manager:
worker:
此实现中可能的漏洞:
一种懒惰的 IO awaiter,在,在构造函数时什么都不做。
在被 co_await 时暂停,并发起 IO 请求,未来等待由调度器唤醒。当前线程轮询可以切入的协程。
awaiter,其中的 await_suspend 负责主要逻辑:awaiter 的 await_resume 返回特定结果。仅运行在用户态 co_context 的信号量
限制 co_spawn 和同类活跃协程的并发量
acquire 分别在栈上创建 awaiter,形成等待链表release 时放出一个release请求,由io_context处理(强制单消费者),放入某个reap_swap$ claude mcp add co_context \
-- python -m otcore.mcp_server <graph>