IO多路复用机制(转)
1.简介
希望通过这篇文章,可以回答以下几个问题?
- 为什么需要IO多路复用?
- 什么是IO多路复用机制?
- IO多路复用的机制该怎么使用?
- epoll比select/poll相比,优势在哪里?
在了解I/O多路复用之前,先来了解流的概念。
1.1流的概念
一个流可以文件、socket、pipe等可以进行IO操作的内核对象。不管是文件,还是套接字,还是管道,我们都可以把他们看作流。
从流中读取数据或者写入数据到流中,可能存在这样的情况:读取数据时,流中还没有数据;写入数据时,流中数据已经满了,没有空间写入了。典型的例子为客户端要从socket流中读入数据,但是服务器还没有把数据准备好。此时有两种处理办法:
- 阻塞,等待数据准备好了,再读取出来返回;
- 非阻塞,通过轮询的方式,查询是否有数据可以读取,直到把数据读取返回。
接下来再来了解以下I/O同步、异步、阻塞、非阻塞的概念。
1.2 I/O同步、异步、阻塞、非阻塞
在IO操作过程中,可能会涉及到同步(synchronous)、异步(asynchronous)、阻塞(blocking)、非阻塞(non-blocking)、IO多路复用(IO multiplexing)等概念。他们之间的区别是什么呢?
以网络IO为例,在IO操作过程会涉及到两个对象:
- 一个是调用这个IO的process (or thread);
- 另外一个是一个就是系统内核(kernel)。
在一个IO操作过程中,以read为例,会涉及到两个过程:
- 等待数据准备好(Waiting for the data to be ready);
- 将数据从内核拷贝到进程中 (Copying the data from the kernel to the process)
这两个阶段是否发生阻塞,将产生不同的效果。
阻塞VS非阻塞
阻塞IO:
- 在1、2阶段都发生阻塞;
- 调用阻塞IO会一直block住进程,直到操作完成;
非阻塞IO:
- 在第1阶段没有阻塞,在第2阶段发生阻塞;
- 调用非阻塞IO会在kernel准备数据的情况下立即返回;
- 非阻塞IO需要不断轮询,查看数据是否已经准备好了;
阻塞与非阻塞可以简单理解为调用一个IO操作能不能立即得到返回应答,如果不能立即获得返回,需要等待,那就阻塞了;否则就可以理解为非阻塞。
同步VS异步
同步IO:
同步IO操作将导致请求的进程一直被blocked,直到IO操作完成。从这个层次来,阻塞IO、非阻塞IO操作、IO多路复用都是同步IO。
异步IO:
异步IO操作不会导致请求的进程被blocked。当发出IO操作请求,直接返回,等待IO操作完成后,再通知调用进程。
多路复用IO
多路复用IO也是阻塞IO,只是阻塞的方法是select/poll/epoll。select/epoll的好处就在于单个process就可以同时处理多个网络连接的IO。它的基本原理是select/epoll这个函数会不断轮询所负责的IO操作,当某个IO操作有数据到达时,就通知用户进程。然后由用户进程去操作IO。
1.3 多路复用概念
I/O多路复用是通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。
阻塞I/O有一个比较明显的缺点是在I/O阻塞模式下,一个线程只能处理一个流的I/O事件。如果想要同时处理多个流,需要多个进程或者多个线程,但是这种方式效率不高。
非阻塞的I/O需要轮询查看流是否已经准备好了,比较典型的方式是忙轮询。
忙轮询
忙轮询方式是通过不停的把所有的流从头到尾轮询一遍,查询是否有流已经准备就绪,然后又从头开始。如果所有流都没有准备就绪,那么只会白白浪费CPU时间。轮询过程可以参照如下:
while true {
for i in stream[]; {
if i has data
read until unavailable
}
}
无差别的轮询方式
为了避免白白浪费CPU时间,我们采用另外一种轮询方式,无差别的轮询方式。即通过引进一个代理,这个代理为select/poll,这个代理可以同时观察多个流的I/O事件。当所有的流都没有准备就绪时,会把当前线程阻塞掉;当有一个或多个流的I/O事件就绪时,就从阻塞状态中醒来,然后轮询一遍所有的流,处理已经准备好的I/O事件。轮询的过程可以参照如下:
while true {
select(streams[])
for i in streams[] {
if i has data
read until unavailable
}
}
如果I/O事件准备就绪,那么我们的程序就会阻塞在select处。我们通过select那里只是知道了有I/O事件准备好了,但不知道具体是哪几个流(可能有一个,也可能有多个),所以需要无差别的轮询所有的流,找出已经准备就绪的流。可以看到,使用select时,我们需要O(n)的时间复杂度来处理流,处理的流越多,消耗的时间也就越多。
最小轮询方式
无差别的轮询方式有一个缺点就是,随着监控的流越来越多,需要轮询的时间也会随之增加,效率也会随之降低。所以还有另外一种轮询方式,最小轮询方式,即通过epoll方式来观察多个流,epoll只会把发生了I/O事件的流通知我们,我们对这些流的操作都是有意义的,时间复杂度降低到O(k),其中k为产生I/O事件的流个数。轮询的过程如下:
while true {
active_stream[] = epoll_wait(epollfd)
for i in active_stream[] {
read or write till unavailable
}
}
select/poll/epoll都是采用I/O多路复用机制的,其中select/poll是采用无差别轮询方式,而epoll是采用最小的轮询方式。
1.4 I/O复用的优势
I/O多路复用的优势并不是对于单个连接能处理的更快,而是在于可以在单个线程/进程中处理更多的连接。
与多进程和多线程技术相比,I/O多路复用技术的最大优势是系统开销小,系统不必创建进程/线程,也不必维护这些进程/线程,从而大大减小了系统的开销。
2. I/O复用函数介绍
下面简单的介绍一下I/O复用函数。
2.1 select函数
系统提供Select函数来实现多路复用输入/输出模型,Select系统调用是用来让我们的程序监视多个文件句柄的状态变化。程序会阻塞在select函数上,直到被监视的文件句柄中有一个或多个发生了状态变化。
函数原型
#include <sys/select.h>
#include <sys/time.h>
int select(int maxfd,fd_set *readset,fd_set *writeset,fd_set *exceptset,const struct timeval *timeout)
参数说明:
- maxfd:需要监视的最大的文件描述符值+1;
- readset:需要检测的可读文件描述符的集合;
- writeset:需要检测的可写文件描述符的集合
- exceptset:需要检测的异常文件描述符的集合
- timeout:超时时间;超时时间有三种情况:
- NULL:永远等待下去,仅在有一个描述字准备好I/O时才返回;
- 0:立即返回,仅检测描述符集合的状态,然后立即返回,并不等待外部事件的发生;
- 特定的时间值: 如果在指定的时间段里没有事件发生,select将超时返回;
函数返回值有三种情况:
- 返回0表示超时了;
- 返回-1,表示出错了;
- 返回一个大于0的数,表示文件描述符状态改变的个数;
fd_set是一个文件描述符集合,可以通过以下宏来操作:
- FD_CLR(inr fd,fd_set* set):用来清除文件描述符集合set中相关fd的位
- FD_ISSET(int fd,fd_set *set):用来测试文件描述符集合set中相关fd的位是否为真
- FD_SET(int fd,fd_set*set):用来设置文件描述符集合set中相关fd的位
- FD_ZERO(fd_set *set):用来清除文件描述符集合set的全部位
2.2 Poll函数
Poll的处理机制与Select类似,只是Poll选择了pollfd结构体来处理文件描述符的相关操作:
struct pollfd {
int fd; /* 文件描述符 */
short events; /* 等待的事件 */
short revents; /* 实际发生了的事件 */
} ;
每一个pollfd结构体都指定了一个文件描述符fd,events代表了需要监听该文件描述的事件掩码,可选的有:
- POLLIN:有数据可读。
- POLLRDNORM:有普通数据可读。
- POLLRDBAND:有优先数据可读。
- POLLPRI:有紧迫数据可读。
- POLLOUT:写数据不会导致阻塞。
- POLLWRNORM:写普通数据不会导致阻塞。
- POLLWRBAND:写优先数据不会导致阻塞。
- POLLMSGSIGPOLL:消息可用。
revents代表文件描述符的操作结果掩码,内核在调用返回时设置这个域。events域中请求的任何事件都可能在revents域中返回,除此之外,revents域还可以包含以下事件:
- POLLER:指定的文件描述符发生错误。
- POLLHUP:指定的文件描述符挂起事件。
- POLLNVAL:指定的文件描述符非法。
poll的函数原型:
# include <poll.h>
int poll ( struct pollfd * fds, unsigned int nfds, int timeout);
参数说明:
- fds:需要被监视的文件描述符集合;
- nfds:被监视的文件描述符数量;
- timeout:超时时间,有三种取值:
- 负数:无限超时,一直等到一个指定事件发生;
- 0:立即返回,并列出准备好的文件描述符;
- 正数:等待指定的时间,单位为毫秒;
poll函数与select函数的最大不同之处在于:select函数有最大文件描述符的限制,一般1024个,而poll函数对文件描述符的数量没有限制。但select和poll函数都是通过轮询的方式来查询某个文件描述符状态是否发生了变化,并且需要将整个文件描述符集合在用户空间和内核空间之间来回拷贝,这样随着文件描述符的数量增加,相应的开销也随之增加。
2.3 epoll函数
epoll是在Linux内核2.6引进的,是select和poll函数的增强版。与select相比,epoll没有文件描述符数量的限制。epoll使用一个文件描述符管理多个文件描述符,将用户关心的文件描述符事件存放到内核的一个事件列表中,这样在用户空间和内核空间只需拷贝一次。
epoll操作是包含有三个接口的:
#include <sys/epoll.h>
int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
epoll_create函数:
- 用创建一个epoll的句柄;
- size用来告诉内核这个监听的数目一共有多大,占用一个fd值;
epoll_ctl函数:
- epoll的事件注册函数;
参数:
- epfd:epoll_create()的返回值;
op:动作,有三种取值:
- EPOLL_CTL_ADD:注册新的fd到epfd中;
- EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
- EPOLL_CTL_DEL:从epfd中删除一个fd;
fd:需要监听的fd;
- event: 告诉内核需要监听什么事件,取值有:
- EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
- EPOLLOUT:表示对应的文件描述符可以写;
- EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
- EPOLLERR:表示对应的文件描述符发生错误;
- EPOLLHUP:表示对应的文件描述符被挂断;
- EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
- EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列;
epoll_wait函数:
- 等待事件的产生;
- 参数:
- events:从内核得到事件的集合;
- maxevents:事件集合的大小;
- timeout:超时时间,0会立即返回,-1表示永久阻塞,正数表示一个指定的值;
工作模式
epoll对文件描述符的操作由两种模式:水平触发LT(level trigger)和边沿触发ET(edge trigger)。默认的情况下为LT模式。LT模式与ET模式的区别在于:
- LT模式:当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序可以不立即处理该事件。下次调用epoll_wait时,会再次响应应用程序并通知此事件。
- ET模式:当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序必须立即处理该事件。如果不处理,下次调用epoll_wait时,不会再次响应应用程序并通知此事件。
ET模式在很大程度上减少了epoll事件被重复触发的次数,因此效率要比LT模式高。epoll工作在ET模式的时候,必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。
2.4 epoll相比于select/poll的优势?
从上面对select/poll/epoll函数的介绍,可以知道epoll与select/poll相比,具有如下优势:
- 监视的描述符数量不受限制,所支持的FD上限是最大可以打开文件的数目;
- I/O效率不会随着监视fd的数量增长而下降。epoll不同于select和poll轮询的方式,而是通过每个fd定义的回调函数来实现的,只有就绪的fd才会执行回调函数。
3.总结
IO复用机制可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立即通知相应程序进行读或写操作。但select,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的
IO多路复用机制(转)的更多相关文章
- IO多路复用机制详解
高性能IO模型浅析 服务器端编程经常需要构造高性能的IO模型,常见的IO模型有四种: (1)同步阻塞IO(Blocking IO):即传统的IO模型. (2)同步非阻塞IO(Non-blocking ...
- 【转】IO多路复用机制详解
高性能IO模型浅析 服务器端编程经常需要构造高性能的IO模型,常见的IO模型有四种: (1)同步阻塞IO(Blocking IO):即传统的IO模型. (2)同步非阻塞IO(Non-blocking ...
- python selectors模块实现 IO多路复用机制的上传下载
import selectorsimport socketimport os,time BASE_DIR = os.path.dirname(os.path.abspath(__file__))''' ...
- IO模型——IO多路复用机制
(1)I/O多路复用技术通过把多个I/O的阻塞复用到同一个select.poll或epoll的阻塞上,从而使得系统在单线程的情况下可以同时处理多个客户端请求.与传统的多线程/多进程模型比,I/O多路复 ...
- 03 高性能IO模型:采用多路复用机制的“单线程”Redis
本篇重点 三个问题: "Redis真的只有单线程吗?""为什么用单线程?""单线程为什么这么快?" "Redis真的只有单线程吗? ...
- 【Java面试】什么是IO的多路复用机制?
"什么是IO的多路复用机制?" 这是一道年薪50W的面试题,很遗憾,99%的人都回答不出来. 大家好,我是Mic,一个工作了14年的Java程序员. 今天,给大家分享一道网络IO的 ...
- 聊聊IO多路复用之select、poll、epoll详解
本文转载自: http://mp.weixin.qq.com/s?__biz=MzAxODI5ODMwOA==&mid=2666538922&idx=1&sn=e6b436ef ...
- IO多路复用和local概念
一.local 在多个线程之间使用threading.local对象,可以实现多个线程之间的数据隔离 import time import random from threading import T ...
- 异步、非阻塞和IO多路复用总结
Nginx是并发处理框架的代表者,很多后台业务都会放在Nginx容器中运行,以实现高吞吐,而Nginx能够支持高并发也是由于使用了异步非阻塞处理模型,本文将用通俗的话讲解异步.同步.阻塞.非阻塞的区别 ...
随机推荐
- 【转】使用vue-cli搭建项目
vue-cli 是一个官方发布 vue.js 项目脚手架,使用 vue-cli 可以快速创建 vue 项目,GitHub地址是:https://github.com/vuejs/vue-cli 一. ...
- 使用 StoryBoard 制作一个能够删除cell的TableView
本篇博客方便自己检索使用.资源链接 下面是制作效果图,点击删除按钮,就能将该cell删除: 下面是主要的代码: #define KSUPER_TAG 20000 #define KDEFAU_TAG ...
- set和multiset容器
set和multiset容器的能力 set 和multiset容器的内部结构通常由平衡二叉树(balanced binary tree)来实现.当元素放入容器中时,会按照一定的排序法则自动排序,默认是 ...
- Flutter实战视频-移动电商-64.会员中心_顶部头像UI布局
64.会员中心_顶部头像UI布局 会员中心的样式 member.dart 清除原来的代码生成一个基本的结构 默认返回一个scaffold脚手架工具,body里面布局使用ListView,这样不会出现纵 ...
- 爬虫代码实现四:采用Hbase存储爬虫数据(2)
导入hbase的jar包,在maven仓库找:进入http://mavenrepository.com/,输入hbase client,选择apache hbase client, 点击进入,选择 点 ...
- spring mvc 处理映射的几种方式
1.Spring MVC bean的nameurl处理映射 <bean class="org.springframework.web.servlet.view.InternalReso ...
- 201621123016 《Java程序设计》第七周学习总结
1. 本周学习总结 1.1 思维导图:Java图形界面总结 2.书面作业 1. GUI中的事件处理 1.1 写出事件处理模型中最重要的几个关键词. 关键词:事件源,事件监听器,事件处理方法,事件适配器 ...
- 在 beforeSend中设置ajax请求的Content-type
$.ajaxSetup({ beforeSend: function (xhr, settings) { if (settings.type == "PO ...
- 2019年最新总结,从程序员到CTO,从专业走向卓越,大牛分享文档pdf与PPT整理
整理大牛分享文档如下,持续更新一线开发架构,技术文档 github链接 网易蜂巢公有容器云架构之路 新浪微博redis优化历程 微博Cache架构设计实践 Go在大数据开发中的经验总结 基于Go构建滴 ...
- 什么是socket ??
socket起源于Unix, 而Unix/Linux基本哲学之一就是"一切皆文件", 都可以用"打开open -> 读写write/read -> 关闭clo ...