{"id":21104167,"url":"https://github.com/codesire-deng/co_context","last_synced_at":"2025-04-04T15:07:06.817Z","repository":{"id":40262057,"uuid":"466772890","full_name":"Codesire-Deng/co_context","owner":"Codesire-Deng","description":"A coroutine framework aimed at high-concurrency io with reasonable latency, based on io_uring.","archived":false,"fork":false,"pushed_at":"2024-07-08T14:27:17.000Z","size":799,"stargazers_count":556,"open_issues_count":8,"forks_count":48,"subscribers_count":7,"default_branch":"main","last_synced_at":"2025-03-28T14:07:02.757Z","etag":null,"topics":["async","coroutine","io-uring","linux","network","uring"],"latest_commit_sha":null,"homepage":"","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Codesire-Deng.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-03-06T15:12:57.000Z","updated_at":"2025-03-23T07:46:55.000Z","dependencies_parsed_at":"2023-12-30T08:24:37.693Z","dependency_job_id":"4a0002e0-46e2-4139-87b2-8ef84e9ddd6c","html_url":"https://github.com/Codesire-Deng/co_context","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Codesire-Deng%2Fco_context","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Codesire-Deng%2Fco_context/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Codesire-Deng%2Fco_context/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Codesire-Deng%2Fco_context/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Codesire-Deng","download_url":"https://codeload.github.com/Codesire-Deng/co_context/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247198449,"owners_count":20900079,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["async","coroutine","io-uring","linux","network","uring"],"created_at":"2024-11-20T00:00:57.205Z","updated_at":"2025-04-04T15:07:06.797Z","avatar_url":"https://github.com/Codesire-Deng.png","language":"C++","funding_links":[],"categories":[],"sub_categories":[],"readme":"简中 | [English](./doc/README_en.md)\n\n# co_context\n\n[![License](https://img.shields.io/badge/License-Apache%202.0-green.svg)](https://github.com/Codesire-Deng/co_context/blob/main/LICENSE)\n[![Platform](https://img.shields.io/badge/Platform-Linux-blue)](https://img.shields.io/badge/Platform-Linux-blue)\n[![Language](https://img.shields.io/badge/Language-C%2B%2B20-red)](https://en.cppreference.com/w/cpp/compiler_support/20)\n[![Ubuntu 22.04 (gcc)](https://github.com/Codesire-Deng/co_context/actions/workflows/ubuntu_gcc.yml/badge.svg)](https://github.com/Codesire-Deng/co_context/actions/workflows/ubuntu_gcc.yml)\n[![Ubuntu 22.04 (clang)](https://github.com/Codesire-Deng/co_context/actions/workflows/ubuntu_clang.yml/badge.svg)](https://github.com/Codesire-Deng/co_context/actions/workflows/ubuntu_clang.yml)\n\nco_context 是一个**协程**并发框架，提供可靠的**性能**和强**易用性**，让 C++ 初学者也能轻松写出高并发程序。\nco_context 基于 Linux [io_uring](https://github.com/axboe/liburing)，其性能通常优于 epoll。\n\n\u003cdetails\u003e\n\u003csummary\u003e不了解协程？\u003c/summary\u003e\n\n我在 Bilibili 上传了[协程系列视频](https://space.bilibili.com/35186937)，请多指教！\n\u003c/details\u003e\n\n## 已有功能\n\n1. 支持 `read` `write` `accept` `timeout` 等 io_uring 提供的所有系统调用，总计 74 个功能。\n2. 并发支持: `any`, `some`, `all`, `mutex`, `semaphore`, `condition_variable`, `channel`。\n3. 调度提示: `yield`, `resume_on`。\n4. 取消 IO/协程：`timeout`, `timeout_at`, `stop_token`。\n\n## 编译和运行\n\n### 依赖\n\n1. GCC 11.3.0 或 Clang 14 或更高。\n2. [可选] [mimalloc](https://github.com/microsoft/mimalloc)  从包管理器或源代码安装。\n3. Linux 内核版本 \u003e= 5.6，建议 \u003e= 5.11，越新越好。\n    - 运行 `uname -r` 即可查看你的内核版本。\n    - 由于开发环境是 Linux 6.0，在其他版本下可能出现兼容性错误。如遇问题，请将报错发到[issue](https://github.com/Codesire-Deng/co_context/issues)，非常感谢！\n    - **docker 将继承宿主机的 Linux 内核版本**。 因此，docker 无法解决 Linux 内核版本过低的问题。\n\n### 编译命令\n\n```bash\ncmake -B build \u0026\u0026 cmake --build build -j\nbuild/example/timer # 跑一个小示例\n```\n\n## 代码示例\n\n### 基础用法\n\n创建一个 `io_context`，用于运行协程：\n\n```cpp\n    using namespace co_context;\n    io_context context;\n```\n\n使用 `task\u003c\u003e` 定义一个 socket 监听协程。`task\u003c\u003e` 就像一个普通的 `void` 函数，但可以在里面使用 `co_await`。\n\n```cpp\ntask\u003c\u003e server(uint16_t port) {\n    acceptor ac{inet_address{port}};\n    for (int sockfd; (sockfd = co_await ac.accept()) \u003e= 0;) {// 异步接受 client\n        co_spawn(session(co_context::socket{sockfd})); // 每个连接生成一个 worker 任务\n    }\n}\n```\n\n继续使用 `task\u003cT\u003e` 描述业务逻辑，例如读取 socket 的内容并输出到 stdout：\n\n```cpp\ntask\u003c\u003e session(co_context::socket sock) {\n    char buf[8192];\n    int nr = co_await sock.recv(buf);\n\n    // 不断接收字节流并打印到stdout\n    while (nr \u003e 0) {\n        co_await lazy::write(STDOUT_FILENO, {buf, (size_t)nr});\n        nr = co_await sock.recv(buf);\n    }\n}\n```\n\n`task\u003cT\u003e` 和普通 `T` 函数一样可以任意嵌套：\n\n```cpp\ntask\u003cint\u003e the_answer() {\n    co_return 42;\n}\ntask\u003c\u003e universe() {\n    printf(\"The answer is %d\\n\", co_await the_answer());\n    co_return;\n}\n```\n\n如何写一个 `main()`：\n\n```cpp\nint main(int argc, const char *argv[]) {\n    if (argc \u003c 3) {\n        printf(\"Usage:\\n  %s hostname port\\n  %s -l port\\n\", argv[0], argv[0]);\n        return 0;\n    }\n\n    io_context context; // 1. 定义一个 io_context\n\n    int port = atoi(argv[2]);\n    if (strcmp(argv[1], \"-l\") == 0) {\n        context.co_spawn(server(port)); // 2. 至少创建一个 task\u003c\u003e\n    } else {\n        context.co_spawn(client(argv[1], port));\n    }\n\n    context.start(); // 3. 启动 io_context 线程\n    context.join();  // 4. 需要时等待 io_context 线程\n\n    return 0;\n}\n\n```\n\n### 更多特性示例\n\n#### 一秒触发器\n\n```cpp\ntask\u003c\u003e my_clock() {\n    for (int cnt = 0;;) {\n        printf(\"Time = %d\\n\", cnt++);\n        co_await timeout(1s);\n    }\n}\n```\n\n#### 网络超时\n\n秒懂的超时控制。\n\n```cpp\ntask\u003c\u003e session(co_context::socket peer) {\n    char buf[8192];\n    int nr = co_await timeout(peer.recv(buf), 3s); // 限时3秒\n\n    while (nr \u003e 0) {\n        co_await lazy::write(STDOUT_FILENO, {buf, (size_t)nr});\n        nr = co_await timeout(peer.recv(buf), 3s); // 限时3秒\n    }\n\n    log_error(-nr);\n}\n\nvoid log_error(int err) {\n    switch (err) {\n        case ECANCELED:\n            log::e(\"timeout!\\n\");\n            break;\n        default:\n            log::e(\"%s\\n\", strerror(err));\n            break;\n    }\n}\n```\n\n#### 负载均衡\n\n每个 `io_context` 代表一个线程。要负载均衡，只需将 `task\u003c\u003e` 分配到合理的 `io_context`。\n\n[示例：echo_server_MT.cpp](./example/echo_server_MT.cpp)\n\n借助 `resume_on()`，可以在运行时任意切换线程。\n\n[示例：resume_on.cpp](./example/resume_on.cpp)\n\n#### Generator\n\n同步执行的生成器，可配合`std::range`。 *需要 g++*\n\n```cpp\nco_context::generator\u003cint\u003e gen_iota(int x) {\n    while (true)\n        co_yield x++;\n}\n\nint main() {\n    using namespace std::views;\n\n    for (int x : gen_iota(1) | drop(5) | take(3)) {\n        std::cout \u003c\u003c x \u003c\u003c \" \";\n    }\n    // 输出 6 7 8\n\n    return 0;\n}\n```\n\n#### 链接 IO\n\n用 `\u0026\u0026` 来链接两个 I/O。链接 I/O 可以减少重入内核态和调度器，增强性能表现。（默认只返回最后一个返回值。）\n\n```cpp\nnr = co_await (\n    peer.send({buf, (size_t)nr})\n    \u0026\u0026 peer.recv(buf)\n);\n```\n\n此例子利用 link_io 大幅增强 echo_server 的性能\n\n#### channel（实验性）\n\n借鉴自 Go 语言的阻塞队列。\n\n[示例：channel.cpp](./example/channel.cpp)\n\n\u003cdetails\u003e\n\n\u003csummary\u003eDraft\u003c/summary\u003e\n\n## 性能\n\nco_context 在开发过程中表现出惊人的性能。早期测试见[我的博客](https://codesire-deng.github.io/2022/06/25/co-context-2/#%E5%B0%8F%E7%BB%93)。下一个开发周期将进行更多测试。\n\n## 协程方案的局限场景\n\n由于内置动态内存分配，基于协程的异步框架可能**不是**性能的最优解，如果你正处于类似 30ns 延迟的极端性能场景，且不在乎编程复杂度，推荐关注 **sender/receiver model**，而无需尝试协程。\n\n## 协程方案的适用场景\n\n如果你希望异步框架能够最佳地平衡「开发、维护成本」和「项目质量、性能」，从而最大化经济效益，推荐你关注协程方案。感性理解：协程 + 内核态 I/O 的性能类似于 Redis 的网络模块。\n\n## 关于缓存友好问题\n\n**co_context** 竭尽所能避免缓存问题：\n\n1. **co_context** 的主线程和任意 worker 的数据交换中没有使用互斥锁，极少使用原子变量。\n2. **co_context** 的数据结构保证「可能频繁读写」的 cacheline 最多被两个线程访问，无论并发强度有多大。这个保证本身也不经过互斥锁或原子变量。（若使用原子变量，高竞争下性能损失约 33%～70%）\n3. 对于可能被多于两个线程读写的 cacheline，**co_context** 保证乒乓缓存问题最多发生常数次。\n4. 在 AMD-5800X，3200 Mhz-ddr4 环境下，若绕过 io_uring，**co_context** 的线程交互频率可达 1.25 GHz。\n5. 在一个本地测试中（I7-8550U 移动端），**单线程**的协程切换的平均延迟为 4.6 ns，代码于 [test/benchmark/lazy_yield](test/benchmark/lazy_yield.cpp)。\n5. 在一个本地测试中（R7-5800X 桌面端），**跨线程**的协程切换的平均延迟为 37 ns，~~代码于 [test/ctx_swtch.cpp](test/ctx_swtch.cpp)。~~\n6. 协程自身的缓存不友好问题（主要由 `operator new` 引起），需要借助其他工具来解决，例如 [mimalloc](https://github.com/microsoft/mimalloc)。\n\n---\n\n## 协程存在的问题\n\n### 弱点\n\n1. 除非编译器优化，每个协程都需要通过 `operator new` 来分配 frame：\n   - 多线程高频率动态内存分配可能引发性能问题；\n   - 在嵌入式或异构（例如 GPU）环境下，缺乏动态内存分配能力，难以工作。\n2. 除非编译器优化，协程的可定制点太多，需要大量间接调用/跳转（而不是内联），同样引起性能问题。\n   - 目前，编译器通常难以内联协程\n   - HALO 优化理论：[P0981R0](http://open-std.org/JTC1/SC22/WG21/docs/papers/2021/p2300r3.html#biblio-p0981r0)\n3. **动态分配**和**间接调用**的存在，导致协程暂时无法成为异步框架的最优方法。\n\n### 拆分子协程？\n\n- 出于性能考虑，不要将大协程拆分为几个小协程，因为会增加动态内存分配次数。\n  - 可以做 placement new 吗？\n\n### 与异步框架高度耦合\n\n1. 暂停和恢复都需要通过异步框架。\n2. 表达式模板的潜力不如 sender/receiver 模型：\n   - 协程是顺序/分支/循环结构，s/r是表达式。\n\n## draft\n\n- 研究 liburingcxx 如何支持多生产者，多消费者并行（线程池中每个线程同时是 IO 生产者和消费者）\n- Coroutine 解决内联和动态内存分配问题\n- 表达式模板解决 task `\u0026\u0026` `||`。\n- 和 `std::execution` 能否兼容\n\n### 线程池实现\n\n- 一个内核线程 polling，一个主线程收集提交、收割推送I/O，其他固定 worker 线程，thread bind core\n- 节能模式：信号量表示允许的 idle worker 线程数量。低延模式：每个 worker 都 polling\n- 每个 worker 自带两条任务队列（一个sqe，一个cqe），固定长度，原子变量，cacheline友好。sqe放不下就放 std::queue，等有空位再放入共享cache。\n- 主线程cqe推送满了就切换到提交sqe\n- 主线程sqe提交满了就切换到推送cqe\n\n### eager_io\n\n一种激进的 IO awaiter，在构造函数中初始化 IO 请求并提交。\n\n在被 `co_await` 时，若 IO 早已完成，则无需让出。否则，需要等待 IO 完成后由调度器唤醒。\n\n#### eager_io 的动机\n\n1. 可以轻易部署并行化的 IO 请求，且对于 caller 协程来说是非阻塞的。还可进化出可取消的协程。\n2. 尽早提交 IO 请求，可能带来更低的延迟。\n\n#### eager_io 的缺点\n\n涉及多线程并行，需要同步 IO 的状态（未完成、已完成）。至少要保证：调度器必须确保 「eager_io 已经知悉 IO 已完成」，否则可能丢数据。\n\n#### eager_io 的实现\n\nTODO: 改用原子变量，弃用检查队列\n\n**co_context** 假设大多数 eager_io 会陷入「等待状态」，以此为优化立足点\n\n1. eager_io 的 coroutine state(promise) 是调度器负责决定由谁销毁（由调度器或者由协程自己）。\n2. eager_io 发起 IO 前，自我标记为「初始状态」「无结果」「无权销毁」，然后发起 IO。\n3. eager_io 在「初始状态」下被 `co_await`，检查结果：\n   1. 为「无结果」，则自我标记为「等待状态」「有权销毁」「有结果」，让出执行权\n   2. 为「有结果」，自我标记为「IO 后状态」（保持「无权销毁」），继续执行。\n   3. 析构时，「有权销毁」则销毁协程，否则自我标记为「待销毁」。\n4. 调度器收割 IO 时，检查协程的标记：\n   1. 为「等待状态」，则将协程加入调度队列，令其自行销毁。\n   2. 为「初始状态」（初始、等待叠加态），向协程标记「有结果」，随后将协程加入检查队列\n5. 调度器完成一轮提交/收割后，轮询检查队列：\n   1. 若协程为「等待状态」，则弹出检查队列，并加入调度队列，令其自行销毁。\n   2. 若协程为「初始状态」或者「IO 后状态」，不管它。\n   3. 若协程为「待销毁」，销毁它，弹出检查队列。\n\nxxx \u003c-\u003e is_detached is_waiting is_ready\n\nmanager:\n\n- ready: xx0 to xx1\n  - 1x1 : manager delete task_info, do not resume.\n  - 001 : worker will delete task_info, do not resume.\n  - 011 : worker will delete task_info, resume\n\nworker:\n\n- wait: x0x to x1x\n  - 11x : wait after detached, logic error\n  - 010 : suspend, worker will delete task_info\n  - 011 : do not suspend, worker will delete task_info\n- detach: 0xx to 1xx\n  - 1x1 : worker will delete task_info\n  - 100 : manager will delete task_info\n  - 110 : detach after waited, logic error\n\n此实现中可能的漏洞：\n\n1. 未反省协程发生异常时的内存模型\n2. 等你来发现……\n\n### lazy_io\n\n一种懒惰的 IO awaiter，在，在构造函数时什么都不做。\n\n在被 `co_await` 时暂停，并发起 IO 请求，未来等待由调度器唤醒。当前线程轮询可以切入的协程。\n\n#### lazy_io 的实现\n\n1. lazy_io 返回一个 `awaiter`，其中的 `await_suspend` 负责主要逻辑：\n   1. 提交一个 IO 请求。\n   2. 找到一个已收割的 IO 请求，恢复它\n2. `awaiter` 的 `await_resume` 返回特定结果。\n3. 析构时，销毁协程。\n\n### semaphore\n\n仅运行在用户态 co_context 的信号量\n\n#### semaphore 的动机\n\n限制 `co_spawn` 和同类活跃协程的并发量\n\n#### semaphore 的实现\n\n1. 参考 std::semaphore，优化 binary_semaphore 的原子变量\n2. 链表栈模拟无锁队列，均摊O(1)\n3. `acquire` 分别在栈上创建 `awaiter`，形成等待链表\n4. `release` 时放出一个release请求，由io_context处理（强制单消费者），放入某个reap_swap\n\n\u003c/details\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcodesire-deng%2Fco_context","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcodesire-deng%2Fco_context","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcodesire-deng%2Fco_context/lists"}