前言
BIO 、多路底层NIO 、复用AIO 总结上篇讲 BIO、原理NIO、前篇AIO 的多路底层基本概念以及一些常见问题,介绍了 NIO 是复用同步非阻塞 ,服务器实现模式为一个线程可以处理多个请求(连接),原理客户端发送的前篇连接请求都会注册到多路复用器selector上,多路复用器轮询到连接有IO请求就进行处理。多路底层那么I/O多路复用器到底是复用如何实现的?本篇我们来一探究竟。
为了加深对 I/O多路复用机制 的原理理解,以及了解到多路复用也有局限性,前篇本着打破砂锅问到底的多路底层精神,在这里我们先回顾下 Unix网络编程中的复用五种IO模型。下篇再继续 对 IO多路复用进行深入的原理学习。
Blocking IO - 阻塞IO NoneBlocking IO - 非阻塞IO IO multiplexing - IO多路复用 signal driven IO - 信号驱动IO asynchronous IO - 异步IOUnix网络编程中的五种IO模型阻塞IO - Blocking IO最传统的一种IO模型,即在读写数据过程中会发生阻塞现象。
当用户线程发出IO请求之后,内核会去查看数据是否就绪,如果没有就绪就会等待数据就绪,而用户线程就会处于阻塞状态,用户线程交出CPU。香港云服务器当数据就绪之后,内核会将数据拷贝到用户线程,并返回结果给用户线程,用户线程才解除block状态。
也许有人会说,可以采用多线程+ 阻塞IO 来解决效率问题,但是由于在多线程 + 阻塞IO 中,每个socket对应一个线程,这样会造成很大的资源占用,并且尤其是对于长连接来说,线程的资源一直不会释放,如果后面陆续有很多连接的话,就会造成性能上的瓶颈。
非阻塞IO - NoneBlocking IO当用户线程发起一个 IO 操作后,并不需要等待,而是马上就得到一个结果。如果结果是一个 error 时,它就知道数据还没有准备好,于是它可以再次发送 IO 操作。一旦内核中的数据准备好了,并且又再次收到了用户线程的请求,那么它马上就将数据拷贝到了用户线程,亿华云计算然后返回。
在非阻塞IO 模型中,用户线程需要不断地询问内核数据是否就绪,也就说非阻塞IO不会交出CPU,而会一直占用CPU。
对于非阻塞IO就有一个非常严重的问题,在while循环中需要不断地去询问内核数据是否就绪,这样会导致CPU占用率非常高,因此一般情况下很少使用while循环这种方式来读取数据。
IO多路复用 - IO multiplexing所谓 I/O 多路复用机制,就是说通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或写就绪),能够通知程序进行相应的读写操作。这种机制的使用需要 select 、 poll 、 epoll 来配合。
在多路复用IO模型中,会有一个内核线程不断地去轮询多个 socket 的状态,只有当真正读写事件发送时,亿华云才真正调用实际的IO读写操作。因为在多路复用IO模型中,只需要使用一个线程就可以管理多个socket,系统不需要建立新的进程或者线程,也不必维护这些线程和进程,并且只有真正有读写事件进行时,才会使用IO资源,所以它大大减少来资源占用。
信号驱动IO - signal driven IO在信号驱动IO模型中,当用户线程发起一个IO请求操作,会给对应的socket注册一个信号函数,然后用户线程会继续执行,当内核数据就绪时会发送一个信号给用户线程,用户线程接收到信号后,便在信号函数中调用IO读写操作来进行实际的IO请求操作。这个一般用于UDP中,对TCP套接字几乎没用,原因是该信号产生得过于频繁,并且该信号的出现并没有告诉我们发生了什么请求。
用户进程可以使用信号方式,当系统内核描述符就绪时将会发送SIGNO给到用户空间,这个时候再发起recvfrom的系统调用等待返回成功提示,流程如下:
先开启套接字的信号IO启动功能,并通过一个内置安装信号处理函数的signaction系统调用,当发起调用之后会直接返回; 其次,等待内核从网络中接收数据报之后,向用户空间发送当前数据可达的信号给信号处理函数; 信号处理函数接收到信息就发起recvfrom系统调用等待内核数据复制数据报到用户空间的缓冲区; 接收到复制完成的返回成功提示之后,应用进程就可以开始从网络中读取数据。异步IO - asynchronous IO前面四种IO模型实际上都属于同步IO,只有最后一种是真正的异步IO,因为无论是多路复用IO还是信号驱动模型,IO操作的第2个阶段都会引起用户线程阻塞,也就是内核进行数据拷贝的过程都会让用户线程阻塞。
总结现代计算机服务器操作系统大部分都是基于linxu实现,为处理高并发而采取NIO的模型,对于支持异步IO模型的系统持有不确定因素。
详见 BIO 、NIO 、AIO 总结
同步与异步的定义
同步:发起一个fn的调用,需要等待调用结果返回,该调用结果要么是期望的结果要么是异常抛出的结果,可以说是原子性操作(要么成功要么失败返回) 异步: 发起一个fn调用,无需等待结果就直接返回,只有当被调用者执行处理程序之后通过“唤醒”手段通知调用方获取结果(唤醒的方式有回调,事件通知等) 小结: 同步和异步关注的是程序之间的通信阻塞与非阻塞的定义
阻塞: 类比线程阻塞来说明,在并发多线程争抢资源的竞态条件下,如果有一个线程已持有锁,那么当前线程将无法获取锁而被挂起,处于等待状态 非阻塞: 一旦线程释放锁,其他线程将会进入就绪状态,具备争抢锁的资格 小结: 阻塞与非阻塞更关注是程序等待结果的状态 由此可知,同步异步与阻塞非阻塞之间不存在关联,关注的目标是不一样的同步IO与异步IO(基于POSIX规范)
同步IO: 表示应用进程发起真实的IO操作请求(recvfrom)导致进程一直处于等待状态,这时候进程被阻塞,直到IO操作完成返回成功提示 异步IO: 表示应用进程发起真实的IO操作请求(recvfrom)导致进程将直接返回一个错误信息,“相当于告诉进程还没有处理好,好了会通知你” 阻塞IO: 主要是体现发起IO操作请求通知内核并且内核接收到信号之后如果让进程等待,那么就是阻塞 非阻塞IO: 发起IO操作请求的时候不论结果直接告诉进程“不用等待,晚点再来”,那就是非阻塞IO模型对比
一句话总结:
阻塞IO与非阻塞IO这是最简单的模型,一般配合多线程来实现。
多路复用(select/poll/epoll)一个线程解决多连接的问题
信号驱动IO模型一种同步IO,更加灵活
异步IO模型高效主流的模型,效率很高。