IO多路复用是一种同步IO模型,用于高效地处理多个输入/输出(I/O)操作,使单个进程或线程能够同时监视多个文件描述符(如套接字、文件等),以确定哪个描述符就绪,可以进行读写操作而不会阻塞。
一、基本概念
在传统的阻塞式 I/O 模型中,当一个进程调用 read 或 write 等 I/O 系统调用时,如果相应的 I/O 设备没有准备好数据或缓冲区已满无法写入,进程会被阻塞,直到 I/O 操作完成。这种方式在处理单个连接时可能比较简单直接,但在处理多个连接时效率低下,因为每个连接都需要一个单独的进程或线程来处理,并且在等待 I/O 操作时会浪费大量的 CPU 时间。
IO 多路复用的核心思想是使用一个或几个系统调用(如 select、poll、epoll 等)来同时监视多个文件描述符,当其中任何一个文件描述符就绪时,系统调用就会返回,告知程序哪个文件描述符可以进行读写操作。这样,程序就可以避免在每个连接上都阻塞等待 I/O 操作,从而提高了系统的并发性和效率。
二、常见的 IO 多路复用实现方式
-
select:select函数是最早出现的 IO 多路复用实现方式之一。- 它通过一个文件描述符集合来监视多个文件描述符的状态,当有文件描述符就绪时,
select函数返回。 select函数的主要缺点是可监视的文件描述符数量有限(通常为 1024),并且每次调用select时都需要将所有要监视的文件描述符从用户空间复制到内核空间,效率较低。
具体看上图,在用户态会维护一个bitmap记录哪些文件打来符需要监听,然后将bitmap复制到内核态,由内核态去遍历文件打开符判断数据是否到来,如果到了,就将已经到了数据的文件打开符标记到bitmap,返回到用户态。用户态再根据bitmap去取数据。 由于bitmap限制了1024位,所以可以监听的文件打开符有限,同时要遍历所有socket、将bitmap来回从用户态和内核态复制,效率也不高。
-
poll:poll函数与select类似,也是通过一个文件描述符数组来监视多个文件描述符的状态。- 与
select相比,poll没有最大文件描述符数量的限制。 - 但是,
poll也存在每次调用都需要将所有要监视的文件描述符从用户空间复制到内核空间的问题。
poll封装了fd对象来存储文件描述符,是否有数据等信息,解决了select长度为1024的问题,其他大体上与select类似。
-
epoll:epoll是 Linux 特有的高性能 IO 多路复用实现方式。epoll通过在内核中维护一个事件表,避免了每次调用都将文件描述符从用户空间复制到内核空间的开销。epoll支持边缘触发(Edge Triggered)和水平触发(Level Triggered)两种模式,可以根据不同的应用场景进行选择。
epoll完全在内核态进行操作,只有数据到来时才去唤醒进程进行数据的获取,所以不需要进行数据来回复制,也没有1024的长度限制,效率是最高的。不过epoll只在linux内核2.6之后的版本才能支持。
三、应用场景
- 网络服务器:在网络服务器中,通常需要同时处理多个客户端的连接请求。使用 IO 多路复用可以使服务器在单个进程或线程中高效地处理多个连接,避免为每个连接创建一个单独的进程或线程,从而减少系统资源的消耗。
- 文件服务器:文件服务器需要同时处理多个文件的读写请求。IO 多路复用可以使文件服务器在单个进程或线程中高效地处理多个文件的读写操作,提高文件服务器的性能。
- 数据库服务器:数据库服务器需要同时处理多个客户端的查询请求。IO 多路复用可以使数据库服务器在单个进程或线程中高效地处理多个客户端的查询请求,提高数据库服务器的性能。
四、优势
- 提高系统的并发性:通过单个进程或线程同时监视多个文件描述符,减少了进程或线程的创建和切换开销,提高了系统的并发性。
- 减少系统资源的消耗:相比于为每个连接创建一个单独的进程或线程,IO 多路复用可以在单个进程或线程中处理多个连接,减少了系统资源的消耗。
- 提高系统的响应速度:当有文件描述符就绪时,IO 多路复用可以立即通知程序进行处理,提高了系统的响应速度。
总之,IO 多路复用是一种高效的 I/O 处理方式,适用于需要同时处理多个 I/O 操作的场景。它可以提高系统的并发性、减少系统资源的消耗,并提高系统的响应速度。