C++ 并发核心模型总结—— 从阻塞 IO 到 Reactor + 协程的完整理解(附 mini epoll + Reactor demo)

张开发
2026/4/3 15:31:58 15 分钟阅读
C++ 并发核心模型总结—— 从阻塞 IO 到 Reactor + 协程的完整理解(附 mini epoll + Reactor demo)
前面学习的内容C 网络服务端主线从线程池到 Reactor 的完整路线图一、为什么要学这套模型很多人学 C 网络编程会陷入这些困惑为什么线程越多反而越慢epoll 到底解决什么问题Reactor 和线程池有什么区别协程为什么突然变得很重要 本质是你没有把 IO模型 调度模型 执行模型打通本文目标帮你一次性理解阻塞 IO非阻塞 IOepollReactorC 协程并用一个mini Reactor demo把它们串起来。二、总纲先记住这5句话阻塞IO线程等数据非阻塞IO线程不等数据epoll告诉你谁有数据Reactor调度谁去处理数据协程让代码可以暂停再继续执行三、阻塞 IO vs 非阻塞 IO1️⃣ 阻塞 IOBlocking IOread(fd, buffer, size);如果没有数据线程被挂起卡住 特点简单 ✔直观 ✔占线程 ❌ 一句话没数据就等卡线程2️⃣ 非阻塞 IONon-blocking IOfcntl(fd, F_SETFL, O_NONBLOCK); read(fd, buffer, size);如果没有数据立刻返回errno EAGAIN 特点不阻塞线程 ✔需要配合机制判断何时再读 ❗ 一句话没数据就走不等四、为什么需要 epoll问题如果只用非阻塞 IOwhile(true) { read(fd); } 会变成疯狂轮询 → CPU 100%epoll 的作用由内核告诉你哪个 fd “现在可以读/写”核心调用epoll_wait(...)返回ready 的 fd 列表 一句话谁可以干活了五、Reactor 模型Reactor 本质事件驱动调度模型核心流程epoll_wait() ↓ 获取 ready fd ↓ 分发 handler ↓ 执行处理逻辑 一句话安排谁去干活关键关系❗ Reactor ≠ epoll✔ Reactor 使用 epoll六、协程C20协程本质用户态执行流控制可暂停/恢复核心能力co_await ...表示执行到一半 → 挂起 → 以后再继续 一句话代码可以停下来再继续执行七、五者关系总图最重要非阻塞 IO↓epoll事件通知↓Reactor调度↓协程 / handler执行八、mini epoll Reactor demo核心实战功能单线程非阻塞 socketepoll 监听Reactor 分发echo server✅ 完整代码#include arpa/inet.h #include errno.h #include fcntl.h #include sys/epoll.h #include sys/socket.h #include unistd.h #include cstring #include functional #include iostream #include unordered_map #include vector class Reactor { public: using Handler std::functionvoid(int); Reactor() { epoll_fd_ epoll_create1(0); } void add(int fd, uint32_t events, Handler handler) { epoll_event ev{}; ev.events events; ev.data.fd fd; epoll_ctl(epoll_fd_, EPOLL_CTL_ADD, fd, ev); handlers_[fd] handler; } void remove(int fd) { epoll_ctl(epoll_fd_, EPOLL_CTL_DEL, fd, nullptr); handlers_.erase(fd); close(fd); } void loop() { std::vectorepoll_event events(64); while (true) { int n epoll_wait(epoll_fd_, events.data(), 64, -1); for (int i 0; i n; i) { int fd events[i].data.fd; handlers_[fd](fd); } } } private: int epoll_fd_; std::unordered_mapint, Handler handlers_; }; int set_nonblocking(int fd) { int flags fcntl(fd, F_GETFL, 0); return fcntl(fd, F_SETFL, flags | O_NONBLOCK); } int main() { int server_fd socket(AF_INET, SOCK_STREAM, 0); set_nonblocking(server_fd); sockaddr_in addr{}; addr.sin_family AF_INET; addr.sin_port htons(8080); addr.sin_addr.s_addr INADDR_ANY; bind(server_fd, (sockaddr*)addr, sizeof(addr)); listen(server_fd, 128); Reactor reactor; // accept handler reactor.add(server_fd, EPOLLIN, [](int fd) { while (true) { int client_fd accept(fd, nullptr, nullptr); if (client_fd -1) break; set_nonblocking(client_fd); reactor.add(client_fd, EPOLLIN, [](int cfd) { char buf[1024]; int n read(cfd, buf, sizeof(buf)); if (n 0) { write(cfd, buf, n); } else { reactor.remove(cfd); } }); } }); std::cout server running :8080\n; reactor.loop(); }九、这个 demo 在做什么1️⃣ epollepoll_wait() 获取 ready fd2️⃣ Reactorhandlers_[fd](fd); 分发执行3️⃣ handlerread / write 执行业务 整体epoll → Reactor → handler十、最核心理解一定要记住阻塞 vs 非阻塞阻塞线程等非阻塞线程不等epoll vs Reactorepoll发现事件Reactor调度事件Reactor vs 协程Reactor什么时候执行协程执行到哪暂停十一、最终架构总结epoll事件 ↓ Reactor调度 ↓ 协程 / handler执行阻塞IO让线程等非阻塞IO让线程不等epoll告诉你什么时候可以干Reactor安排谁去干协程让你干活还能暂停继续进阶建议你可以继续进阶手写 Reactor Connection 封装Reactor 线程池多 Reactor 模型Reactor 协程接近 Go / Netty进阶总纲C 高性能服务端进阶路线—— 从 epoll Reactor 到多线程与协程的系统化路径

更多文章