# I/O多路复用技术(multiplexing) https://www.zhihu.com/question/28594409 https://www.zhihu.com/question/28594409 下面举一个例子,模拟一个tcp服务器处理30个客户socket。 假设你是一个老师,让30个学生解答一道题目,然后检查学生做的是否正确,你有下面几个选择: 1. 第一种选择:按顺序逐个检查,先检查A,然后是B,之后是C、D。。。这中间如果有一个学生卡主,全班都会被耽误。 这种模式就好比,你用循环挨个处理socket,根本不具有并发能力。 2. 第二种选择:你创建30个分身,每个分身检查一个学生的答案是否正确。 这种类似于为每一个用户创建一个进程或者线程处理连接。 3. 第三种选择,你站在讲台上等,谁解答完谁举手。这时C、D举手,表示他们解答问题完毕,你下去依次检查C、D的答案,然后继续回到讲台上等。此时E、A又举手,然后去处理E和A。。。 这种就是IO复用模型,Linux下的select、poll和epoll就是干这个的。将用户socket对应的fd注册进epoll,然后epoll帮你监听哪些socket上有消息到达,这样就避免了大量的无用操作。此时的socket应该采用**非阻塞模式**。 这样,整个过程只在调用select、poll、epoll这些调用的时候才会阻塞,收发客户消息是不会阻塞的,整个进程或者线程就被充分利用起来,这就是事件驱动,所谓的**reactor模式**。 ----- 首先,要从你常用的IO操作谈起,比如read和write,通常IO操作都是阻塞I/O的,也就是说当你调用read时,如果没有数据收到,那么线程或者进程就会被挂起,直到收到数据。 ![](https://i.imgur.com/dtQfotv.png) ## 舉例 当服务器需要处理1000个连接的的时候,而且只有很少连接忙碌的,那么会需要1000个线程或进程来处理1000个连接,而1000个线程大部分是被阻塞起来的。由于CPU的核数或超线程数一般都不大,比如4,8,16,32,64,128,比如4个核要跑1000个线程,那么每个线程的时间槽非常短,而线程切换非常频繁。这样是有问题的: 1. 线程是有内存开销的,1个线程可能需要512K(或2M)存放栈,那么1000个线程就要512M(或2G)内存。 2. 线程的切换,或者说上下文切换是有CPU开销的,当大量时间花在上下文切换的时候,分配给真正的操作的CPU就要少很多。 那么,我们就要引入**非阻塞I/O**的概念,非阻塞IO很简单,通过fcntl(POSIX)或ioctl(Unix)设为非阻塞模式,这时,当你调用read时,如果有数据收到,就返回数据,如果没有数据收到,就立刻返回一个错误,如EWOULDBLOCK。这样是不会阻塞线程了,但是你还是要不断的轮询来读取或写入 ![Uploading file..._876ue52zu]() 于是,我们需要引入IO多路复用的概念。多路复用是指使用一个线程来检查多个文件描述符(Socket)的就绪状态,比如调用select和poll函数,传入多个文件描述符,如果有一个文件描述符就绪,则返回,否则阻塞直到超时。得到就绪状态后进行真正的操作可以在同一个线程里执行,也可以启动线程执行(比如使用线程池) ![](https://i.imgur.com/KzxSCJF.png) ## redis方面解答 关于I/O多路复用(又被称为“事件驱动”),首先要理解的是,操作系统为你提供了一个功能,当你的某个socket可读或者可写的时候,它可以给你一个通知。这样当配合非阻塞的socket使用时,只有当系统通知我哪个描述符可读了,我才去执行read操作,可以保证每次read都能读到有效数据而不做纯返回-1和EAGAIN的无用功。写操作类似。操作系统的这个功能通过select/poll/epoll/kqueue之类的系统调用函数来使用,这些函数都可以同时监视多个描述符的读写就绪状况,这样,多个描述符的I/O操作都能在一个线程内并发交替地顺序完成,这就叫I/O多路复用,这里的“复用”指的是复用同一个线程。以select和tcp socket为例,所谓可读事件,具体的说是指以下事件: 以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来读取和清除该错误。 ###### tags: `觀念重點區`