基于 IOCP 的协程调度器——零基础深入浅出 C++ 协程 基于 IOCP 的协程调度器——零基础深入浅出 C 协程什么是协程为什么需要协程在传统的C编程中我们通常使用函数调用来组织代码。当函数A调用函数B时A会暂停执行等待B返回后才能继续。这种“同步”模型简单直观但在处理I/O操作如网络读写、文件读写时会导致线程被阻塞浪费CPU资源。协程Coroutine是一种更轻量级的并发单元。它允许函数在执行过程中挂起suspend让出CPU给其他任务并在未来某个时刻恢复resume。与线程不同协程的切换完全由程序控制没有操作系统内核的介入因此开销极低。理解协程的关键在于协程不是多线程而是协作式多任务。多个协程可以在单个线程中交替执行每个协程主动让出执行权而不是被抢占。## C20 协程基础C20引入了标准协程支持其核心概念包括-协程句柄coroutine_handle用于操作协程的指针可以恢复或销毁协程。-promise 对象管理协程的状态包括存储返回值或异常。-awaitable 对象定义了await_ready、await_suspend、await_resume三个关键方法控制协程何时挂起。让我们从一个最简单的协程示例开始cpp#include coroutine#include iostream// 一个简单的协程返回类型struct SimpleTask { struct promise_type { SimpleTask get_return_object() { return SimpleTask{std::coroutine_handlepromise_type::from_promise(*this)}; } std::suspend_never initial_suspend() { return {}; } // 创建后立即执行 std::suspend_never final_suspend() noexcept { return {}; } // 完成后不挂起 void return_void() {} void unhandled_exception() { std::terminate(); } }; std::coroutine_handlepromise_type handle; explicit SimpleTask(std::coroutine_handlepromise_type h) : handle(h) {}};SimpleTask helloWorld() { std::cout Hello from coroutine! std::endl; co_return; // 协程结束}int main() { auto task helloWorld(); // 创建并执行协程 std::cout Back in main std::endl; return 0;}运行结果Hello from coroutine!Back in main这个例子展示了协程的基本结构。co_return标记了协程的结束点。注意initial_suspend()返回std::suspend_never意味着协程创建后立即执行而不是挂起。## 从挂起到恢复await 操作符协程的强大之处在于能够挂起和恢复。co_await操作符让协程在等待某个操作时挂起稍后再恢复cpp#include coroutine#include iostream#include thread// 一个可等待对象模拟异步操作struct DelayedTask { bool await_ready() { return false; } // 总是挂起 void await_suspend(std::coroutine_handle handle) { // 在新线程中等待1秒后恢复协程 std::thread([handle]() { std::this_thread::sleep_for(std::chrono::seconds(1)); handle.resume(); // 恢复协程 }).detach(); } int await_resume() { return 42; } // 返回结果};struct Task { struct promise_type { Task get_return_object() { return {}; } std::suspend_never initial_suspend() { return {}; } std::suspend_always final_suspend() noexcept { return {}; } void return_void() {} void unhandled_exception() {} };};Task asyncFunction() { std::cout Start async operation... std::endl; int result co_await DelayedTask{}; // 挂起1秒 std::cout Got result: result std::endl;}int main() { asyncFunction(); std::cout Main thread continues... std::endl; std::this_thread::sleep_for(std::chrono::seconds(2)); // 等待协程完成 return 0;}输出Start async operation...Main thread continues...(1秒后)Got result: 42这里的关键是await_suspend中我们保存了协程句柄并在异步操作完成后调用handle.resume()恢复执行。这正是协程调度器的核心思想。## IOCPI/O完成端口简介IOCP是Windows平台上最高效的I/O模型之一特别适用于高并发网络服务器。它的工作方式类似于“事件驱动”应用程序将I/O请求提交给操作系统当操作完成时系统将完成通知放入一个队列应用程序从队列中取出通知进行处理。IOCP的关键API包括-CreateIoCompletionPort创建完成端口-GetQueuedCompletionStatus等待完成通知-PostQueuedCompletionStatus手动投递完成通知## 基于IOCP的协程调度器设计现在让我们将IOCP和协程结合起来。我们的调度器将1. 使用IOCP管理异步I/O操作2. 当I/O操作完成时自动恢复等待该操作的协程3. 支持协程挂起和恢复的无缝切换### 核心实现cpp#include windows.h#include coroutine#include iostream#include list#include mutex// IOCP协程调度器class IocpScheduler {private: HANDLE iocp_; std::mutex mtx_; std::liststd::coroutine_handle ready_queue_; public: IocpScheduler() { iocp_ CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0); if (!iocp_) throw std::runtime_error(Failed to create IOCP); } ~IocpScheduler() { if (iocp_) CloseHandle(iocp_); } HANDLE getIocp() const { return iocp_; } // 向IOCP注册套接字 void registerSocket(SOCKET sock) { CreateIoCompletionPort((HANDLE)sock, iocp_, (ULONG_PTR)this, 0); } // 投递异步读操作 void asyncRead(SOCKET sock, char* buffer, int size, std::coroutine_handle handle) { // 保存协程句柄到overlapped结构简化起见使用全局变量 static auto current_handle handle; OVERLAPPED* overlapped new OVERLAPPED{0}; overlapped-hEvent (HANDLE)current_handle; // 存储协程句柄 WSABUF buf {size, buffer}; DWORD flags 0; WSARecv(sock, buf, 1, NULL, flags, overlapped, NULL); } // 主调度循环 void run() { while (true) { DWORD bytes_transferred; ULONG_PTR completion_key; OVERLAPPED* overlapped; BOOL result GetQueuedCompletionStatus( iocp_, bytes_transferred, completion_key, overlapped, INFINITE); if (result overlapped) { // 恢复等待该I/O操作的协程 auto* handle (std::coroutine_handle*)overlapped-hEvent; if (handle) { handle-resume(); } delete overlapped; } } }};// 可等待的异步读操作struct AsyncReadAwaiter { SOCKET sock; char* buffer; int size; IocpScheduler* scheduler; bool await_ready() { return false; } void await_suspend(std::coroutine_handle handle) { // 投递异步读操作并传递协程句柄 scheduler-asyncRead(sock, buffer, size, handle); } int await_resume() { return 0; } // 返回读到的字节数};// 使用示例struct EchoTask { struct promise_type { EchoTask get_return_object() { return {}; } std::suspend_never initial_suspend() { return {}; } std::suspend_always final_suspend() noexcept { return {}; } void return_void() {} void unhandled_exception() {} };};EchoTask echoSession(SOCKET client, IocpScheduler scheduler) { char buffer[1024]; while (true) { // 异步读取客户端数据 co_await AsyncReadAwaiter{client, buffer, sizeof(buffer), scheduler}; // 处理数据简化直接回显 send(client, buffer, sizeof(buffer), 0); }}## 高级用法协程池与负载均衡在实际的高性能服务器中我们通常需要管理大量的并发协程。下面展示如何创建一个协程池并实现简单的负载均衡cpp#include vector#include queue#include functional// 协程池class CoroutinePool {private: std::vectorstd::coroutine_handle pool_; std::queuestd::coroutine_handle pending_; std::mutex mtx_; public: CoroutinePool(size_t size) { pool_.reserve(size); // 预创建协程实际使用中需要更复杂的初始化 for (size_t i 0; i size; i) { // 创建并挂起协程 auto handle createSuspendedCoroutine(); pool_.push_back(handle); } } // 分配一个可用的协程 std::coroutine_handle allocate() { std::lock_guardstd::mutex lock(mtx_); if (!pending_.empty()) { auto handle pending_.front(); pending_.pop(); return handle; } return {}; // 无可用协程 } // 释放协程回池 void release(std::coroutine_handle handle) { std::lock_guardstd::mutex lock(mtx_); pending_.push(handle); } private: std::coroutine_handle createSuspendedCoroutine() { // 创建一个立即挂起的协程 struct SuspendTask { struct promise_type { SuspendTask get_return_object() { return {}; } std::suspend_always initial_suspend() { return {}; } std::suspend_always final_suspend() noexcept { return {}; } void return_void() {} void unhandled_exception() {} }; }; auto task [](CoroutinePool* pool) - SuspendTask { co_await std::suspend_always{}; // 挂起等待分配 // 协程体 co_return; }(this); // 获取协程句柄实际实现需要更复杂 return {}; // 简化处理 }};## 总结通过本文我们从零开始学习了C协程的基础概念并逐步构建了一个基于IOCP的协程调度器。关键点回顾1.协程的本质是协作式多任务通过co_await和co_return控制执行流程2.IOCP的作用提供高效的I/O完成通知机制与协程的挂起/恢复模式完美契合3.调度器设计将IOCP完成事件直接映射为协程的恢复操作实现零阻塞的异步编程这种组合的优势在于-性能极高协程切换开销远低于线程-代码直观异步代码看起来像同步代码-可扩展性强轻松管理数万个并发连接对于想要深入学习的读者建议进一步研究- 如何使用std::generator实现流式处理- 协程与RAII的资源管理- 跨平台协程调度器如Linux的epoll 协程记住协程不是银弹但在I/O密集型场景中它是简化异步编程的强大工具。

本月热点