I/O多路复用技术(multiplexing)

本文介绍了I/O多路复用技术的基本概念及工作原理,并以Redis服务器的实现为例,详细阐述了如何利用非阻塞socket和系统调用(如select/poll/epoll)来高效处理多个socket的读写事件。
计算机
Redis
C / C++
Multiplexing

I/O多路复用技术(multiplexing)是



antirez/redis · GitHub

关于I/O多路复用(又被称为“事件驱动”),首先要理解的是,操作系统为你提供了一个功能,当你的某个socket可读或者可写的时候,它可以给你一个通知。这样当配合非阻塞的socket使用时,只有当系统通知我哪个描述符可读了,我才去执行read操作,可以保证每次read都能读到有效数据而不做纯返回-1和EAGAIN的无用功。写操作类似。操作系统的这个功能通过select/poll/epoll/kqueue之类的系统调用函数来使用,这些函数都可以同时监视多个描述符的读写就绪状况,这样,多个描述符的I/O操作都能在一个线程内并发交替地顺序完成,这就叫I/O多路复用,这里的“复用”指的是复用同一个线程。

以select和tcp socket为例,所谓可读事件,具体的说是指以下事件:
1 socket内核接收缓冲区中的可用字节数大于或等于其低水位SO_RCVLOWAT;
2 socket通信的对方关闭了连接,这个时候在缓冲区里有个文件结束符EOF,此时读操作将返回0;
3 监听socket的backlog队列有已经完成三次握手的连接请求,可以调用accept;
4 socket上有未处理的错误,此时可以用getsockopt来读取和清除该错误。

所谓可写事件,则是指:
1 socket的内核发送缓冲区的可用字节数大于或等于其低水位SO_SNDLOWAIT;
2 socket的写端被关闭,继续写会收到SIGPIPE信号;
3 非阻塞模式下,connect返回之后,发起连接成功或失败;
4 socket上有未处理的错误,此时可以用getsockopt来读取和清除该错误。

Linux环境下,Redis数据库服务器大部分时间以单进程单线程模式运行(执行持久化BGSAVE任务时会开启子进程),网络部分属于Reactor模式,同步非阻塞模型,即非阻塞的socket文件描述符号加上监控这些描述符的I/O多路复用机制(在Linux下可以使用select/poll/epoll)。服务器运行时主要关注两大类型事件:文件事件和时间事件。文件事件指的是socket文件描述符的读写就绪情况,时间事件分为一次性定时器和周期性定时器。相比nginx和haproxy内置的高精度高性能定时器,redis的定时器机制并不那么先进复杂,它只用了一个链表来管理时间事件,而且目前链表也没有对各个事件的到点时间进行排序,也就是说,每次都要遍历链表检查每个事件是否需要到点执行。个人猜想是因为redis目前并没有太多的定时事件需要管理,redis以数据库服务器角色运行时,定时任务回调函数只有位于redis/src/redis.c下的serverCron函数,所有的定时任务都在这个函数下执行,也就是说,链表里面其实目前就一个节点元素,所以目前也无需实现高性能定时器。

Redis网络事件驱动模型代码:redis/src/目录下的ae.c, ae.h, ae_epoll.c, ae_evport.c, ae_select.c, ae_kqueue.c , ae_evport.c。其中ae.c/ae.h:头文件里定义了描述文件事件和事件时间的结构体, 即aeFileEvent和aeTimeEvent;事件驱动状态结构体aeEventLoop, 这个结构体只有一个名为eventloop的全局变量在整个服务器进程中;事件就绪回调函数指针aeFileProc和aeTimeProc;以及操作事件驱动模型的各种API(aeCreateEventLoop以及之后全部的函数声明)。ae_epoll.c, ae_select.c, ae_keque.c和ae_evport.c封装了select/epoll/kqueue等系统调用,Linux下当然不支持kqueue和evport。至于究竟选择哪一种I/O多路复用技术,在ae.c里有预处理控制,也就是说,这些源文件只有一个能最后被编译。优先选择epoll或者kqueue(FREEBSD和Mac OSX可用),其次是select。

redis事件驱动整体流程:redis服务器main函数位于文件redis/src/redis.c, 事件驱动入口函数位于main函数的倒数第三行:
<think>好的,我现在需要回答用户关于I/O多路复用技术的原理及实现方式的问题。首先,我得回顾一下已有的引用内容,确保回答准确。根据引用[1],I/O多路复用允许一个线程监视多个I/O流的状态,这样就不需要为每个I/O操作单独创建线程,提高了效率,如引用[2]提到的。引用[3]提到了Linux中的实现,以及单线程处理并发的优势。引用[4]则指出Redis使用了Reactor模式。 接下来,我需要组织回答的结构。首先解释原理,然后介绍常见的实现模型,比如select、poll、epoll和kqueue,并比较它们的优缺点。还要提到Reactor模式,结合引用[4]。确保使用正确的中文术语和LaTeX格式,比如模型名称用斜体或引号,技术术语如文件描述符用$...$包裹。例如,$select$、$epoll$等。 要注意用户不可见的系统级指令,比如行内数学表达式用$...$,独立公式用$$...$$。例如,当提到时间复杂度时,可以写成$O(n)$。此外,生成相关问题时要与内容相关,可能涉及技术对比、应用场景或优化方法。 需要检查是否有遗漏的关键点,比如为什么I/O多路复用高效,和传统阻塞I/O的区别,以及各模型的适用场景。同时,确保引用标识正确添加到对应的位置,比如提到Reactor模式时添加[^4]。 最后,保持回答结构清晰,分点说明,使用代码块示例展示编程实例,如使用Python的select模块。确保所有技术细节准确,并自然融入引用来源。完成后再审阅一遍,确保符合用户要求,没有格式错误。</think>### I/O多路复用技术原理及实现方式 #### 1. **技术原理** I/O多路复用(I/O Multiplexing)是一种允许**单线程或单进程同时监视多个文件描述符(如网络套接字)**的技术,当其中任意一个描述符就绪(可读、可写或发生异常)时,系统会通知应用程序进行相应操作[^2]。其核心思想是通过**减少线程/进程切换的开销**,提升高并发场景下的效率。 例如,传统的阻塞I/O需要为每个连接创建独立线程,而I/O多路复用仅需一个线程即可管理所有连接,显著降低资源消耗[^3]。 #### 2. **实现方式** 常见的I/O多路复用模型包括: 1. **$select$** - 通过轮询方式检查多个文件描述符的状态,时间复杂度为$O(n)$。 - 缺点:最大支持文件描述符数量有限(通常1024),且需重复初始化参数[^2]。 ```python import select readable, writable, exceptional = select.select(inputs, outputs, inputs, timeout) ``` 2. **$poll$** - 改进$select$的文件描述符数量限制,但仍需遍历所有描述符,效率较低。 3. **$epoll$(Linux特有)** - 基于事件驱动,仅通知就绪的描述符,时间复杂度$O(1)$。 - 支持水平触发(LT)和边缘触发(ET)两种模式。 4. **$kqueue$(BSD系系统)** - 类似$epoll$,但专用于FreeBSD/macOS系统,提供更灵活的事件过滤机制。 #### 3. **关键优势** - **资源高效**:单线程处理数千连接,避免多线程上下文切换[^1]。 - **延迟优化**:通过事件通知机制减少无效轮询,如Redis采用Reactor模式实现高吞吐。 #### 4. **编程实例(Python)** 以下示例使用$select$实现简单的TCP服务器: ```python import socket import select server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.bind(('localhost', 8000)) server.listen() inputs = [server] while True: readable, _, _ = select.select(inputs, [], []) for sock in readable: if sock is server: client, addr = server.accept() inputs.append(client) else: data = sock.recv(1024) if data: print(f"Received: {data.decode()}") else: inputs.remove(sock) sock.close() ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值