io多路复用

什么是io多路复用

单线程或者单进程同时检测多个文件描述符是否可以执行io操作的能力了

解决什么问题

应用程序往往要处理多条事件流的事件,而cpu单核在同一时间只能做一件事,一种解决方式就是将cpu进行时分复用。在计算机系统中,我们用线程和进程来表示一条执行流,通过不同的线程和进程在操作系统内部的调度,来做到对cpu的时分复用。
线程/进程有几个成本

  1. 线程/进程创建成本
  2. cpu切换不同的线程/进程成本
  3. 多线程的资源竞争
    io多路复用的解决本质就是用更少的资源完成更多的事

linux的io处理模型

  1. 阻塞io
  2. 非阻塞io
  3. io多路复用
  4. 信号驱动io
  5. 异步io

阻塞io

这是最常用的简单的io模型。阻塞io意味着当我们发起一次io操作后,要一直等待成功或者失败后才返回,在这期间程序不能做任何其他的事情。阻塞io只能对单个文件描述符进行操作,如read和write。

非阻塞io

我们发起io时,通过对文件描述符设置O_NONBLOCKE flag来指定该文件描述符的io操作为非阻塞。非阻塞io通常发生在一个for虚幻中,,因为每次进行io操作时,要么io操作成功,要不io操作会阻塞时返回错误EWOULDBLOCK/EAGAIN,然后再根据需要进行下一次for循环操作,这种类似轮询的方式会浪费很多不必要的cpu资源。和阻塞io一样,非阻塞io也是通过read和write来进行操作的,也只能对单个描述符进行操作

IO多路复用

有三种方式selelct/poll/epoll,他们的共同点:首先都会对一组文件描述符进行相关事件的注册,然后阻塞以等待某些事件发生或者等待超时。对于三种机制而言,不同数量的文件描述符对性能的影响是不同的

信号驱动io

信号驱动io是利用信号机制,让内核告诉应用程序文件描述符的相关事件。但在网络环境中 ,和socket相关的读写事件太多了,我们没有办法在sigio对应的信号处理函数中区分不同的事件,sigio只能在io事件单一的情况下使用,比如监听端口的socket,因为只有客户端发起新的链接的时候才会产生sigio信号

异步io

异步io和信号驱动io差不多,不一样的地方是:它相比较信号驱动io需要在程序中完成数据从用户态到内核态(或者反方向)的copy,异步io把copy这一步完成后,再通知应用程序。我们使用的事aio_read和aio_write
同步io和异步io

  1. 同步io指的是程序会一直阻塞程序到io操作如read或者write完成
  2. 异步io指的是哦操作不会当前程序的继续执行

io多路复用的方案

select

select的方法有select pselect FD_CLR FD_ISSET FD_SET FD_ZERO
select的调用会阻塞到有文件描述符可以进行io操作或者被信号打断或者超时才会返回
select将监听三种不同的需要进行的io操作。readfds是需要进行读操作的文件描述符,writefds是需要进行写操作的文件描述符,exceptfds是需要进行异常处理的文件描述符。这三个参数通过返回null来表示不需要监听
当select返回的时候,都会进行select过滤,只会留下需要进行io的文件描述符
FD_XX 系列的函数是用来操作文件描述符组合文件描述符的关系
FD_ZERO 用来清空文件描述符组,每次调用select都需要清空一次
FD_SET 用来将一个文件添加到文件描述符组里
FD_CLR 用来将一个文件描述符移除文件描述符组
FD_ISSET 检测一个文件描述符是否在组中,用来检测那些文件描述符可以进行io操作
select 可同时监听的文件描述符数量可以通过FS_SETSIZE来限制的,在linux中是1024,但是是可以设置的,但随着这个数的增加,效率会降低.pselect 和select 大体是一样的,有些细节上的区别

poll

poll的函数有 poll/ppoll/pollfd(fd, events, revents)
和select的三组文件描述符组不同的是,poll只有一个pollfd数组,每个元素都表示一个需要监听io操作事件的文件描述符。events是我们需要关心的事件,revents是内核检测到的事件。

epoll

epoll的函数有 epoll_create/epoll_create1/epoll_ctl/epoll_wait/epoll_pwait
epoll_create/epoll_create1 用来创建一个epoll实例,epoll_ctl用来添删改要检测的文件描述符,epoll_wait用来阻塞等待可以执行的io直到超时

level-triggerd and edge-triggerd

level-triggerd表示只要有io操作可以进行,比如某个文件描述符有数据可读,每次调用epoll_wait都会通知程序可以进行io操作
edge-triggerd表示只有文件描述符发生状态变化的时候,调用epoll_wait,会通知应用程序可以进行io操作,如果第一次没有全部读完数据,第二次没有新数据进入,则不会返回可读。
select和poll都是level-trigger , epoll是两种可选

原文

https://gocn.vip/topics/10090

### IO多路复用的概念 IO多路复用是一种同步I/O模型,允许一个线程同时监控多个文件描述符(如套接字)。这种技术使得程序可以在等待多个输入源中的任何一个变为可用时不会被阻塞。当任意一个文件描述符准备好了读取或写入操作,则会立即通知应用程序去处理相应的工作[^1]。 ### 实现原理 在Linux系统下,可以通过`select`, `poll` 或者更高效的`epoll` 来实现这一功能。这些函数可以让进程一次性监视多个文件描述符,并告知哪些已经准备好进行通信活动。具体来说: - **Select**: 可以监听一定数量的文件描述符集合,在指定时间内检查它们是否有待处理的数据。 - **Poll**: 类似于`select`但是没有最大文件数目的限制,并且性能更好一些因为不需要每次调用都重新构建文件列表。 - **Epoll**: 是一种更为先进的接口,特别适合大量并发连接的情况。它可以注册感兴趣的事件并只报告那些确实发生了变化的对象,从而减少了不必要的上下文切换和资源消耗[^4]。 对于每一个可能发生变化的状态——比如可读、可写或是异常情况发生——都可以设置回调函数以便及时响应。这种方式不仅提高了效率而且简化了编程逻辑。 ### 应用场景 在网络服务端开发领域广泛应用着IO多路复用的技术,尤其是在高负载情况下需要保持大量的活跃TCP连接时表现尤为突出。例如Web服务器(Nginx), 缓存数据库(Redis) 都采用了类似的架构来优化其性能[^5]。 #### Nginx 和 Redis 的例子 这两个软件均采用Reactor模式下的IO多路复用来支持大规模并发请求: - 对于Nginx而言, 主要负责接收新到来的HTTP请求(`accept`)而具体的业务逻辑则由工作进程中完成. - 而像Redis这样的键值存储系统则是完全依赖单个工作循环就能高效地应对成千上万的同时在线客户端. ```python import select import socket server_socket = socket.socket() server_socket.bind(('localhost', 8080)) server_socket.listen(5) inputs = [server_socket] while True: readable, writable, exceptional = select.select(inputs,[],[]) for s in readable: if s is server_socket: client_socket, addr = s.accept() inputs.append(client_socket) else: data = s.recv(1024) if not data: inputs.remove(s) s.close() else: print(f"Received {data.decode()}") ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值