深入理解IO模型

啥是IO

IO是英文Input Output的缩写,意为输入输出。IO操作根据设备类型分为网络IO,文件IO,内存IO,内存IO的速度远远大于文件IO和网络IO的速度,对于文件IO可以通过把磁盘升级为固态硬盘来提升速度,对于网络IO可以通过购买独享带宽和高速网卡来提升速度。

基础知识

Linux用户空间和内核空间

对 32 位操作系统而言,它的寻址空间(虚拟地址空间,或叫线性地址空间)为 4G(2的32次方)。也就是说一个进程的最大地址空间为 4G。操作系统的核心是内核(kernel),它独立于普通的应用程序,可以访问受保护的内存空间,也有访问底层硬件设备的所有权限。为了保证内核的安全,现在的操作系统一般都强制用户进程不能直接操作内核具体的实现方式基本都是由操作系统将虚拟地址空间划分为两部分,一部分为内核空间,另一部分为用户空间。针对 Linux 操作系统而言,最高的 1G 字节(从虚拟地址 0xC0000000 到 0xFFFFFFFF)由内核使用,称为内核空间。而较低的 3G 字节(从虚拟地址 0x00000000 到 0xBFFFFFFF)由各个进程使用,称为用户空间。——《Linux就该这么学》

Linux 操作系统和驱动程序运行在内核空间,应用程序运行在用户空间,两者不能简单地使用指针传递数据。
在这里插入图片描述

文件描述符

在Linux中,一切皆文件,不仅仅数据可以看做是文件,外部设备如磁盘,硬件设备,Socket套接字,进程,线程等都可以看做是文件。

Linux 的内核将所有外部设备都看做一个文件来操作,对一个文件的读写操作会调用内核提供的系统命令(api),返回一个file descriptor(fd,文件描述符)。而对一个socket的读写也会有响应的描述符,称为socket fd(socket文件描述符),描述符就是一个数字,指向内核中的一个结构体(文件路径,数据区等一些属性)。在Linux下对文件的操作是利用文件描述符(file descriptor)来实现的。

IO的操作过程

IO的操作主要分为两步

  1. 用户进程调用内核的read接口,查看内核空间有没有数据,等待内核准备数据
  2. 将内核数据拷贝的用户空间

这两步操作的区别是不同IO模型的区别

阻塞与非阻塞

  • 阻塞:用户线程发起一个请求,调用了一个 函数,在等待这个函数返回结果之前,当前的线程是处于挂起状态,当前线程什么都不能干,就等着获取结果
  • 非阻塞::用户线程发起一个请求,调用了一个 函数,在等待这个函数返回结果之前,当前的线程是处于运行状态,当前线程是可以的继续处理其他任务,但要时不时的去看下是否有结果了

同步与异步

  • 同步:用户线程发起了一个请求,要获得这个请求的返回结果,需要用户线程不断地轮询是否返回了结果
  • 异步:用户线程发起了一个请求,要获得这个请求的返回结果,由请求的服务方通知用户线程已经返回了结果

阻塞IO

为啥叫阻塞IO呢,当用户线程发出 IO 请求之后,内核会去查看数据是否就绪,如果没有就绪就会等待数据就绪,而用户线程就会处于阻塞状态,用户线程交出 CPU。当数据就绪之后,内核会将数据拷贝到用户线程,并返回结果给用户线程,用户线程才解除 block 状态。

典型的阻塞 IO 模型的例子为:data = socket.read();如果数据没有就绪,就会一直阻塞在 read 方法。以下图片来自与:https://segmentfault.com/a/1190000014850886
阻塞IO模型

非阻塞IO

与阻塞IO不同的是,当用户线程发起一个 read 操作后,并不需要等待,而是马上就得到了一个结果。如果结果是一个error 时,它就知道数据还没有准备好,于是它可以再次发送 read 操作。

如果内核中的数据准备好了,并且又再次收到了用户线程的请求,那么它马上就将数据拷贝到了用户线程,然后返回。所以事实上,在非阻塞 IO 模型中,用户线程需要不断地询问内核数据是否就绪,也就说非阻塞 IO不会交出 CPU,而会一直占用 CPU。

非阻塞IO一般只针对网络IO有效,当我们在socket的选项设置中使用O_NONBLOCK时,这个socket的send()或者recv()就会采用非阻塞方式。 对于磁盘IO非阻塞IO并不产生效果。

while(true){
	data = socket.read();
	if(data!= error){
		//处理数据
		break;
	}
}

非阻塞IO有一个致命缺点,因为要不断地询问内核数据准备好了没有,会导致CPU占用率非常高。以下图片来自 https://segmentfault.com/a/1190000014850886
非阻塞IO模型

多路复用IO

我们听说的最多的Java NIO就是多路复用IO。在多路复用 IO模型中,会有一个内核线程不断去轮询多个 socket 的状态,只有当 socket 真正有读写事件时,才真正调用实际的 IO 读写操作。

因为在多路复用 IO 模型中,只需要使用一个线程就可以管理多个socket,系统不需要建立新的进程或者线程,也不必维护这些线程和进程,并且只有在真正有socket 读写事件进行时,才会使用 IO 资源,所以它大大减少了资源占用。

在 Java NIO 中,是通过 selector.select()去查询每个通道是否有到达事件,如果没有事件,则一直阻塞在那里,因此这种方式会导致用户线程的阻塞。多路复用 IO 模式,通过一个线程就可以管理多个 socket,只有当socket 真正有读写事件发生才会占用资源来进行实际的读写操作。

既然都是轮询,那为啥多路复用IO比非阻塞IO效率高呢?

因为在非阻塞 IO 中,不断地询问 socket 状态时通过用户线程去进行的,而在多路复用 IO 中,轮询每个 socket 状态是内核在进行的,内核的效率要比用户线程要高的多!以下图片来自 https://segmentfault.com/a/1190000014850886

多路复用IO

信号驱动IO

在信号驱动 IO 模型中,当用户线程发起一个 IO 请求操作,会给对应的 socket 注册一个信号函数,然后用户线程会继续执行,当内核数据就绪时会发送一个信号给用户线程,用户线程接收到信号之后,便在信号函数中调用 IO 读写操作来进行实际的 IO 请求操作。

在这里插入图片描述

异步IO

异步 IO 模型才是最理想的 IO 模型,在异步 IO 模型中,当用户线程发起 read 操作之后,立刻就可以开始去做其它的事。而另一方面,从内核的角度,当它受到一个 asynchronous read 之后,它会立刻返回,说明 read 请求已经成功发起了,因此不会对用户线程产生任何 block。

然后,内核会等待数据准备完成,然后将数据拷贝到用户线程,当这一切都完成之后,内核会给用户线程发送一个信号,告诉它 read 操作完成了。也就说用户线程完全不需要实际的整个 IO 操作是如何进行的,只需要先发起一个请求,当接收内核返回的成功信号时表示 IO 操作已经完成,可以直接去使用数据了。

也就说在异步 IO 模型中,IO 操作的两个阶段都不会阻塞用户线程,这两个阶段都是由内核自动完成,然后发送一个信号告知用户线程操作已完成。用户线程中不需要再次调用 IO 函数进行具体的读写

信号驱动模型不同的是,在信号驱动模型中,当用户线程接收到信号表示数据已经就绪,然后需要用户线程调用 IO 函数进行实际的读写操作;而在异步 IO 模型中,收到信号表示 IO 操作已经完成,不需要再在用户线程中调用 IO 函数进行实际的读写操作。
异步IO模型

总结

  • 同步阻塞IO(BIO):适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。JDK1.4之前的唯一选择

  • 多路复用IO(NIO):适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器。JDK1.4之后开始支持

  • 异步IO(AIO):适用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持。

最后,举几个不是很恰当的例子吧

  • 阻塞IO:第一天我去饭店吃饭,告诉老板我要吃什么(用户进程发起IO请求),然后老板去后厨看看做好了没有(内核查看数据是否准备好),这期间我一直在柜台等着,什么都不能干(用户进程被阻塞),直到老板把饭端出来,我再自己端到座位上去吃(内核把数据拷贝到用户空间)

  • 非阻塞IO:第二天我去饭店吃饭,告诉老板我要吃什么(用户进程发起IO请求),然后老板说饭还没做好呢(内核立即返回一个错误),然后我可能去打把游戏(用户进程不需要等待,可以做其他事情),但是我需要时不时去问问老板我的饭做好了没有(用户进程轮询内核数据是否准备好),如果饭做好了,并且我去柜台问了,老板就会把饭端给我(内核数据拷贝)

  • 多路复用IO:第三天还是跟上面一样,只是我说了要吃什么之后,不要在柜台等待,也不需要不断地去看做好了没有,当我的饭做好了之后,老板会在广播里说:红烧肉盖饭做好了(通过广播这个线程管理多个连接),这里老板的广播不光是为我一个人服务。

  • 信号驱动IO:第四天跟多路复用的饭店一样,我不需要在柜台等待,也不需要去看做好了没有,这家饭店的老板是我的朋友,所以我有一个专属的通道(注册的信号函数),当我的饭做好了,老板会在专属通道通知我,然后我过去取餐,这里老板的专属通道只属于我一个人

  • 异步IO:第五天去饭店,今天的饭店老板还是熟人,我还是有一个专属通道,与第四天不同的是,今天的老板和我非常铁,当我的饭做好了,老板会在专属通道通知我,然后不用我过去取餐,老板亲自给我端过来,在等饭期间我想干什么干什么,只要等着饭端上桌就OK。

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值