基本概念

同步/异步

同步是指 在发送一个请求时,在该请求返回结果之前,就一直不返回。就是说,这件事做不完,不会做下一件事。 异步是指 发送完请求就返回,不会等待请求响应。当该请求实际完成后,通过回调、通知等方式通知调用者。

阻塞/非阻塞

阻塞是指在结果返回前,当前线程会被挂起(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操作,并将控制权返还给事件分离器