IO 模型(BIO、NIO、多路复用)

IO 模型

BIO,阻塞IO

调用 receive 获取数据的时候,如果获取不到,会一直阻塞

一个连接,就要一个线程处理,那么当应用连接数量非常多的时候,就需要非常多的线程来处理,效率太低了

简单的说,大量线程带来的开销包括:

  1. 线程的内存开销
  2. 线程上下文切换的开销,包括保存和加载上下文,以及由于上下文切换导致的缓存不命中。

NIO,非阻塞IO

调用 receive 获取数据的时候,如果获取不到消息,会直接返回,可以使用一个线程管理多个连接,但是需要忙轮询,挨个去询问数据有没有到达,存在太多无效的询问了

多路复用

底层基于是 NIO,非阻塞IO
使用单个线程,监听多个 fd 文件描述符,可以通过多路复用选择器,通过不同的函数,获取到就绪的 fd,然后自己再去 receive 把数据从内核缓冲区拷贝到用户缓冲区中,这个过程是阻塞的,需要应用程序自己负责读写,所以多路复用属于同步 IO

多路复用选择器

select

每次调用 select 函数,都需要将 fd_set 的集合,从用户空间拷贝到内核缓冲区,返回结果的时候又需要从内核缓存区拷贝到用户空间

拿到函数的结果之后,无法直接知道具体是哪个 fd 就绪了,还需要 O(n) 的时间复杂度来遍历集合,来获取哪个 fd 就绪了

监听的 fd 数量、连接的个数有上限,默认不超过 1024 个

poll

和 select 类似,效率没有太大的提升

使用链表来存储 fd,没有了监听 fd 连接的个数上限,但是链表查找的时间复杂度是 O(n),当监听的 fd 数量多了之后,效率就会变差

拿到函数的结果之后,还需要 O(n) 的时间复杂度来遍历,获取哪个 fd 就绪了

epoll

对select 和 poll进行了巨大的改变

epoll实例中的红黑树保存要监听的FD,理论上无上限,而且增删改查的效率都非常高,性能也不会随着监听FD的数据增多而显著下降

每个FD只需要执行一次epoll-ctl添加到红黑树,以后每次epoll_wait无需传递任何参数,无需重复拷贝FD到内核空间

内核会将就绪的FD直接拷贝到用户空间的指定位置,用户进程无需遍历所有的FD,就能知道就绪的FD是谁

select和poll都只提供了一个函数——select或者poll函数。
而epoll提供了三个函数:epoll_create,epoll_ctl 和 epoll_wait,epoll_create 是创建一个 epoll 句柄;epoll_ctl 是注册要监听的事件类型;epoll_wait 则是等待事件的产生

epoll_create
创建 epoll 结构体

epoll_ctl
往红黑树中添加、移除 fd,当 fd 事件就绪之后,会触发函数回调,将就绪的 fd 放到就绪链表中

epoll_wait
返回就绪链表,通过 O(1) 的时间复杂度,就可以获取到就绪的 fd,然后自己再去 receive 把数据从内核缓冲区拷贝到用户缓冲区中

AIO,异步IO

把数据从内核缓冲区拷贝到用户缓冲区中,这个过程也不是阻塞的,异步I/O则无需应用程序自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间

你可能感兴趣的:(IO,模型,nio,bio,IO模型,多路复用)