更新日期:2026-10-04。本文区分当前实现、可补充的调度策略与标准 C++23 库的技术边界;后续任务按表中编号跟踪,不把候选方案当作已实现能力。当前实现入口见架构,既有优化记录见整改 Spec。
GMP 是 Go 运行时的组织模型:G 是 goroutine,M 是操作系统线程,P 是执行 Go 代码所需的调度及资源上下文,不等于物理 CPU。CMP 的 Task 和 worker 分别承担部分 G、M 职责,目前没有独立 P 层。Go 运行时说明
CMP 已能把多个协作式任务复用到少量 worker,但这不等于完整 GMP。本文依据当前源码和上游运行时说明,不提供 CMP 与 Go 的性能胜负结论。
| 能力 | CMP 当前实现 | 与 GMP 的差别 |
|---|---|---|
| 任务与挂起恢复 | Task 持有协程帧、值或异常 | 无栈、惰性、单消费者,不等同 goroutine |
| 多线程执行 | ThreadPool 固定 worker | 没有 G/M/P 绑定、解绑和重新绑定 |
| 就绪队列与分发 | pool 内共享 FIFO,worker 竞争领取 | 没有每个 P 的本地队列 |
| 空闲休眠与唤醒 | 条件变量等待,入队通知 | 没有协调 spinning/idle worker 的策略 |
| 主动让出与迁移 | co_await scheduler.schedule(),可在其他 worker 恢复 |
必须到达显式挂起点,不能迁移正在运行的任务 |
| 等待时释放执行线程 | 异步事件、AsyncMutex、TCP | 部分原语直接在通知线程恢复,不统一经过调度队列 |
| 定时调度 | RunLoop 的单调时钟 timer 堆 | 尚未整合到多 worker 调度体系 |
| 原生网络等待 | TCP 使用 Asio 和独立 I/O driver | 只覆盖 TCP,结果通过显式 return Scheduler 交付 |
| 阻塞工作隔离 | run_blocking 与独立 pool | 同步函数仍占用线程,不是透明的系统调用接管 |
Go 的 netpoll 与 goroutine 等待/就绪状态直接衔接。CMP 提供相近的异步等待效果,但没有照搬该集成方式。
下表全部为后续范围。下一次处理时先复核源码,形成单项设计,明确可观察行为,再实施和验收。
| 编号 | 候选能力 | 当前缺口及最低验收要求 | 已有入口 |
|---|---|---|---|
| GMP01 | P 层与统一并行度预算 | 当前仅配置各 pool 的 worker 数;需定义预算覆盖范围和调整规则,证明调整时不丢任务、不重复执行 | O09/O11 |
| GMP02 | 本地队列与全局注入队列 | 目前只有共享 FIFO;明确跨队列顺序、取消获胜点和关闭排空,测量共享竞争是否下降 | O05 |
| GMP03 | work stealing | 目前没有偷取协议;保证唯一领取、取消竞争和关闭时所有队列排空,验证不均衡负载与公平性 | O05 |
| GMP04 | runnext、批量转移与局部性优化 | 明确顺序和时间预算变化,不能让短任务反复交接饿死其他任务;测单 worker 与多 worker | O05 |
| GMP05 | 动态 worker 与阻塞补偿 | 仅能可靠处理显式标记的阻塞区间;验证嵌套阻塞、线程上限、创建失败及退出回收 | O06/O13 |
| GMP06 | 自旋与休眠自适应 | 当前为条件变量通知;验证无丢唤醒、空闲 CPU 开销与突发负载延迟,不能只测吞吐 | O05/O12 |
| GMP07 | 协作式执行预算 | 可提供检查点或 yield_if_needed;调用方必须到达检查点,验证有检查点时的让出行为,不承诺抢占无挂起长循环 | O11/O12 |
| GMP08 | 类似 sysmon 的监测 | 可发现长任务、积压和停滞;定义采样成本及通知方式,不能把“已检测”当作“已抢占” | O12 |
| GMP09 | 调度追踪与运行时指标 | 当前无完整公共接口;定义任务关联、线程/重入/异常合同,测默认关闭及开启时的开销 | O12 |
| GMP10 | timer、I/O 与 worker 统一调度 | 当前分属 RunLoop、IoContext 和 ThreadPool;定义完成归属、唤醒与关闭协议,验证线程亲和和排空 | O09/O11 |
Go 的本地队列、runnext、偷取和 worker 管理见调度器源码。CMP 的队列评估已有测量与合同取舍;工作窃取不能被视为保持当前全局 FIFO 领取语义的直接替换。
最低交付:实现或保留方案的依据、受影响契约、正确性/故障/关闭回归、同环境前后样本,以及实际验证平台。仅完成设计或测量时,不将状态改为“已实现”。O 编号对应整改 Spec及执行结果。
以下结论限定为:标准 C++23 无栈协程、纯库、不修改编译器,并兼容任意现有 C++ 调用。放宽这些前提后,部分能力可以借助编译器插桩、平台汇编或 stackful fiber 实现,需要独立架构与兼容性设计。
| 能力 | 原因与可行替代 |
|---|---|
| Go 式异步抢占 | 库不能把正在执行普通指令的 Task 任意变成可重新调度的挂起帧;当前采用显式让出,可进一步设计协作式预算 |
| 在普通同步函数深处挂起整条调用链 | 标准协程的挂起机制不会自动改造其调用的普通函数;需逐层异步适配或改变运行时基础 |
| 透明接管任意阻塞函数 | 系统调用、第三方库和普通锁都可能阻塞 worker;需异步接口、显式 offload 或受限平台拦截 |
| Go 式可搬迁、自动伸缩的协程栈 | Task 保存挂起所需的帧状态,没有独立的完整调用栈;搬迁普通 C++ 栈还涉及栈内指针、ABI 和编译器信息 |
这是基于 C++ 挂起语义与恢复前置条件作出的技术判断。Go 的抢占实现也依赖安全点、编译器信息和 OS 机制,并非任意指令位置均可切换;其动态栈同样属于语言运行时协作。
工作窃取只能取得尚未运行的任务,不能收回正在执行无挂起长循环的 worker。OS 抢占某个线程,也不等于 CMP 能把该线程上的 Task 放回协程队列。
使用 README 的独立项目配置,将以下内容保存为 src/main.cpp。程序每批最多累加 1,000 个数,然后通过下一次 schedule 重新排队;输出 499999500000。
import std;
import mcpplibs.cmp;
namespace cmp = mcpplibs::cmp;
cmp::Task<std::uint64_t> sum(cmp::ThreadPool::Scheduler workers) {
std::uint64_t result {};
for (std::uint64_t base {}; base < 1'000'000; base += 1'000) {
co_await workers.schedule();
for (auto value = base; value < base + 1'000; ++value) {
result += value;
}
}
co_return result;
}
int main() {
cmp::ThreadPool workers { 1 };
cmp::RunLoop loop {};
std::println("{}", loop.run(sum(workers.get_scheduler())));
}这里的批量限制由业务代码提供,不是运行时的时间片保证。when_all、TaskGroup、协作式取消属于已有组合能力;channel/select、DNS/TLS 属于后续同步或 I/O 能力,另见扩展评估,不与 GMP 核心调度机制混为一项。