1、阻塞、非阻塞 、同步异步
不论是磁盘IO、还是网络IO,都分为数据准备和数据读写。以网络IO为例,数据准备相当于服务器监听客户端有没有数据过来;数据读写就是监听到有数据过来该如何去处理数据。阻塞/非阻塞,同步/异步 就是这两种状态的体现。
阻塞与非阻塞:对一个线程来说,阻塞表现形式就是当前线程会阻塞到这个线程的运行的数据准备好,非阻塞的方式就是立刻返回,会通过返回值进行判断
同步:当客户端发送请求给服务端,在等待服务端响应的请求时,客户端不做其他的事情。当服务端做完了才返回到客户端。这样的话客户端需要一直等待
异步:当客户端发送给服务端请求时,在等待服务端响应的时候,客户端可以做其他的事情,这样节约了时间
2、Unix/Linux上的五种IO模型
- 同步阻塞IO(bloking IO)
- 同步非阻塞IO(non-blocking IO)
- 多路复用IO(multiplexing IO)
- 信号驱动式IO(signal-driven IO)
- 异步IO(asynchronous IO)
同步是需要主动等待消息通知,而异步是被动接收消息通知,通过回调、通知等方式来被动获取消息。
同步阻塞IO:在这个IO模型中,用户空间的应用程序执行一个系统调用(recvform),这会导致应用程序阻塞
同步非阻塞IO:非阻塞的recvform系统调用调用之后,进程并没有被阻塞,内核马上返回给进程,如果数据还没准备好,此时会返回一个error
。进程在返回之后,可以执行其他任务,然后再发起recvform系统调用,重复上面的过程,循环往复的进行recvform系统调用(轮询)
多路复用IO:由于轮询占据了很大一部分过程,轮询会消耗大量CPU。因此如果有循环查询任务完成状态,有任何任务完成就去处理它,这就是IO多路复用。比如elect、poll、epoll
信号驱动IO:允许Socket进行信号驱动IO,并安装一个信号处理函数,进程继续运行并不阻塞,当数据准备好时,进程会收到一个SIGIO信号,可以在信号处理函数中调用I/O操作函数处理数据
异步非阻塞IO:用户进程进行aio_read系统调用之后,无论内核数据是否准备好,都会直接返回给用户进程,然后用户态进程可以去做别的事情
信号驱动和多路复用的区别
- 信号驱动I/O通过信号来通知应用程序进行I/O操作,而I/O复用通过系统调用和事件集合来监听和通知就绪的文件描述符。
- 信号驱动I/O通常适用于少量的文件描述符,而I/O复用适用于同时监听多个文件描述符的场景。
- 信号驱动I/O需要应用程序设置信号处理函数来响应事件,而I/O复用通过系统调用返回就绪的文件描述符,应用程序可以在主循环中处理这些就绪的事件。
- I/O复用在效率上通常优于信号驱动I/O,因为信号的处理需要发生上下文切换,并且信号在高负载情况下可能会丢失
2、Reactor模型
反应器设计模式是一种实践处理模式,用于处理多个并发的服务器请求,然后服务处理程序对传入的请求进行解复用,并同步发送给关联的请求处理程序。
重要组件:Event事件、Reactor反应堆、Demultiplex时间分发器、Eventhandler事件处理器
muduo库的Multiple Reactors模型如下:
3、select和poll的优缺点
select的缺点:
- 单个进程能够监视的文件描述符的数量存在最大限制,通常是1024
- 内核 / 用户空间内存拷贝问题,select需要复制大量的句柄数据结构,产生巨大的开销
- select返回的是含有整个句柄的数组,应用程序需要遍历整个数组才能发现哪些句柄发生了事件
- select的触发方式是水平触发,应用程序如果没有完成对一个已经就绪的文件描述符进行IO操作,
- 那么之后每次select调用还是会将这些文件描述符通知进程
poll使用链表保存文件描述符,因此没有了监视文件数量的限制,但其他三个缺点依
然存在
4、epoll原理以及优势
poll通过在Linux内核中申请一个简易的文件系统(文件系统一般用什么数据结构实现?B+树,磁盘IO消耗低,效率很高)
- 调用epoll_create()建立一个epoll对象
- 调用epoll_ctl向epoll对象中添加这100万个连接的套接字
- 调用epoll_wait监听发生的事件的fd资源
LT模式,水平触发模式:内核数据没被读完,就会一直上报数据。
ET模式,触发模式:内核数据只上报一次。
muduo采用的是LT(水平触发):
- 不会丢失数据或者消息:应用没有读取完数据,内核是会不断上报的低
- 低延迟处理
- 跨平台