基本概念
同步/异步
同步
是指 在发送一个请求时,在该请求返回结果之前,就一直不返回。就是说,这件事做不完,不会做下一件事。
异步
是指 发送完请求就返回,不会等待请求响应。当该请求实际完成后,通过回调、通知等方式通知调用者。
阻塞/非阻塞
阻塞
是指在结果返回前,当前线程会被挂起(cpu不会给该线程分配时间片,次此时线程暂停,不可执行)。与同步相比,同步至少没有结果就不返回,大多数情况下,此时线程还是激活的,还可以处理别的事,只是没有返回结果而已。
非阻塞
是指如果不能立即返回结果,该函数不会阻塞当前线程,而是立即返回。
同步/异步 vs. 阻塞/非阻塞
同步/异步
是指访问数据的机制
,二者的区别其实就是,数据读写(拷贝)的时候是否阻塞
。
- 同步是指主动请求并等待I/O操作完,当数据ready后,在读写的时候必须是阻塞的(就是说,数据从内核拷贝到用户空间时,当前的用户空间线程需要阻塞,等待数据拷贝完)。
- 异步是指主动请求数据后,便可以返回处理其他事,I/O操作完后会通知该该请求调用方(等“通知”),即使是在数据读写时也不会阻塞。
阻塞/非阻塞
是指当进程访问的数据没有ready时,进程是等待还是返回,这其实是函数内部实现机制的区别
。二者的区别其实就是,应用程序的调用是否立即返回
。
I/O多路复用
在处理I/O操作时,如果需要同时处理多个I/O请求,可以使用多线程或者I/O多路复用技术进行处理。
I/O多路复用通过把多个I/O的阻塞复用到同一个select的阻塞上,从而可以使系统利用单线程处理多个I/O连接请求
。这种方式,相比多线程来说,系统开销小
。
目前支持I/O多路复用的系统调用有select,pool,epool等。
select模型
- select模型大部分linux/unix系统都支持。
- 通过select函数发送I/O请求后,线程阻塞,一直到数据准备完毕,把数据从内核拷贝到用户空间,所以select是同步阻塞模式。
- 特点:
- 单个进程能监控的fd数目有限制
poll模型
与select类似,不过能监听的并发连接数没限制,因为是用链表存储
epoll模型
epoll是linux特有的事件处理机制。
- 能监听的并发连接数没有限制
- 效率提升:只关心活跃的fd(即达到ready状态的fd),与总连接数无关。
- 内存拷贝:使用内存映射(mmap)技术完成数据从内核传递到用户空间的功能(内核与用户空间共享同一块内存),加速了用户空间与内核空间传递数据的效率,减少了复制的开销。
- 由于epoll在内核中的实现是通过fd的callback函数进行消息传递的,在活跃的fd数量较少的情况下,性能比select和poll都要好,如果活跃的fd比较多的情况,性能也会下降。
水平触发/边缘触发 todo
epoll使用方法
- step1: 创建epoll文件描述符
int epoll_create(int size)
说明:创建好epoll句柄后,会占用一个fd,所以使用完epoll后需要调用close()关闭,释放调该fd(fd的总数是有限的)。
- step2: 注册fd
/*参数epfd是epoll_create的返回值*/
/*
* 参数op表示动作,用三个宏表示:
* 1) EPOLL_CTL_ADD——注册新的fd到epfd中;
* 2) EPOLL_CTL_MOD——修改已经注册的fd的监听事件;
* 3) EPOLL_CTL_DEL:从epfd中删除一个fd;
*/
/*参数fd代表要监听的fd*/
/*参数event告诉内核要做什么事*/
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
- step3: 收集epoll监控的事件中,发生的事件
/*参数events是分配好的epoll_event结构体数组,epoll将会把发生的事件赋值到events数组中*/
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
I/O多路复用模式
I/O多路复用机制一般都依赖一个事件多路分离器
,分离器负责将来自I/O事件源的读写事件分离出来,并分发到对于的read/write事件处理器。根据事先注册的I/O事件及相应的事件处理函数(回调函数),事件分离器事件类型调用相应的事件处理函数。
与事件分离器相关的模式有reactor和proactor,其中reactor模式采用同步I/O,proactor模式采用异步I/O(reactor和proactor是一种I/O设计模式)。
reactor模式
工作流程如下:
- 注册读就绪事件和相应的事件处理器
- 事件分离器等待事件发生(读就绪事件)
- 事件到来,激活分离器,调用相应的读事件处理器
- 事件处理器完成实际的读操作,并处理读到的数据,注册新的事件,并返还控制权
proactor模式
工作流程如下:
- 事件处理器发起异步读操作(操作系统需要支持异步I/O),此时,事件处理器不关心I/O就绪事件,只关心I/O完成事件
- 事件分离器等待读完成事件
- 在分离器等待过程中,操作系统内核线程进行事件的读操作,并将结果存于用户自定义缓冲区,然后通知分离器读操作完成
- 事件处理器读取用户缓冲区中的数据,然后启动一个新的异步I/O操作,并将控制权返还给事件分离器