知识卡片

I/O多路复用的由来:从资源池到事件通知机制

普通读书笔记卡

内容

[[PPC与prefork:每连接一进程模型的三大瓶颈]]和TPC最核心的问题是每个连接都要独占一个进程/线程,连接结束进程就销毁,代价很大。第一步优化很自然:不再为每个连接单独创建进程,而是建一个资源池(进程池/线程池),让一个进程可以处理多个连接。但这样立刻带来新问题:单连接单进程时,进程可以用”read→业务处理→write”这套顺序流程,没数据可读就阻塞在read上完全没问题;但一个进程要同时服务多个连接时,如果阻塞在某一个连接的read上,其他连接哪怕已经有数据可读,这个进程也没法去处理,高性能根本无从谈起。最直接的应对是把read改成非阻塞、让进程不断轮询所有连接——这确实能避免卡死在某一个连接上,但方式并不优雅:轮询本身要消耗CPU,而且一个进程如果要同时管几千上万个连接,轮询的效率会很低。真正优雅的方案是让进程只在真正”有数据”的连接上才动手处理,这正是I/O多路复用技术的由来,它有两个关键实现点:一是让多条连接共用同一个阻塞对象,进程只需要在这一个阻塞对象上等待,而不必逐个轮询所有连接(常见实现有select、epoll、kqueue等);二是某条连接真正有新数据可处理时,由操作系统主动通知进程,进程才从阻塞状态被唤醒去处理。I/O多路复用和线程池结合起来,才真正同时解决了PPC和TPC”进程/线程浪费”以及朴素轮询”CPU浪费”这两层问题,为后续的Reactor模式打下了基础。

参考来源

- 位置:《从零开始学架构》第19讲《单服务器高性能模式:Reactor与Proactor》"Reactor"开篇(源文件:_epub-src/OEBPS/text00001.html) - 结论依据:原文说明"如果一个进程处理多个连接,进程阻塞在某个连接的 read 操作上,此时即使其他连接有数据可读,进程也无法去处理……为了能够更好地解决上述问题……只有当连接上有数据的时候进程才去处理,这就是 I/O 多路复用技术的来源",并列出I/O多路复用的两个关键实现点,直接支撑本卡片结论。 - 原始内容:解决这个问题的最简单的方式是将 read 操作改为非阻塞,然后进程不断地轮询多个连接……只有当连接上有数据的时候进程才去处理,这就是 I/O 多路复用技术的来源……当多条连接共用一个阻塞对象后,进程只需要在一个阻塞对象上等待,而无须再轮询所有连接。