IO模型是一个与多线程并发密切相关的概念,本文重点介绍IO模型的几个基本概念,阻塞与非阻塞,同步与异步。
阻塞与非阻塞
阻塞和非阻塞关注的是程序(线程或进程)在等待调用结果时的状态,一般可能是等待I/O操作的结果。
阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回或继续执行。当前线程被挂起以后,CPU可以处理其他线程。
默认情况下,所有套接字都是阻塞的。进程调用 recvfrom() 后挂起,内核等待外部IO响应,IO完成传送数据到内核buffer,数据再从内核buffer复制到用户空间。
阻塞IO模型
非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程,当前线程会继续往下执行。线程虽然可以处理别的事情,但还需要不断轮询,去看看调用是否已经有结果。
非阻塞IO模型
在上图的非阻塞IO模型中,应用进程没有阻塞,也就是没有挂起,进程一直在check数据有没有传送到内核buffer中。这种情况下进程一直占据CPU,造成忙等的结果。
以餐厅点餐做类比,阻塞IO属于店内排队就餐,非阻塞IO可以不用排队,但点完餐之后,是不是得要去看一下,我点的东西可以取了吗。
非阻塞IO:要不时地看一眼屏幕,是否轮到自己取餐
同步与异步
同步与异步关注的是程序(线程与进程)与内核调用之间的消息同步机制,与阻塞、非阻塞所关注的对象不同,这一点要注意区分。
所谓同步,就是在线程发起一个调用(I/O操作)时,在没有得到结果之前,该调用就不返回。但是一旦调用返回,就得到返回值了。
所谓异步,就是线程发起调用之后,这个调用就直接返回了,调用者不会立刻得到结果。被调用者会通过状态、通知或回调函数等方式返回结果。
网络IO模型
在阻塞与非阻塞、同步与异步的概念基础上,网络IO模型可以大致分为5类:阻塞IO、非阻塞IO、IO多路复用、信号驱动式IO、异步IO。阻塞IO和非阻塞IO已经在前面介绍过,下面主要看一下另外三种模型。
IO多路复用
和阻塞I/O所不同的是,IO多路复用可以用 select 系统调用同时阻塞多个I/O操作,而且可以同时对多个读操作或写操作的I/O函数进行检测,直到有数据可读或可写时,才调用真正的I/O读写操作(recvfrom),也就是说一个线程可以响应多个请求。
IO多路复用
信号驱动IO
又称为事件驱动IO。首先开启套接字的信号驱动式IO功能,并且通过 sigaction 系统调用安装一个信号处理函数,该调用立即返回,进程没有被阻塞,继续工作;当数据准备好的时候,内核为该进程产生 SIGIO 的信号,随后可以在信号处理函数中调用 recvfrom 读取数据,并通知主循环数据已经准备好,或直接通知主循环让它来读取数据。
信号驱动IO
异步IO
在异步IO模型中,当用户线程收到通知时,数据已经被内核读取完毕,并放在了用户线程指定的缓冲区内,内核在IO完成后通知用户线程直接使用即可。
异步IO
与多路复用模型的区别在于,当进程得到内核的通知时,异步IO模型中数据已经在用户空间了;而多路复用模型中,数据还在内核控件,需要应用进程去内核空间里面拷贝。
如果拿外卖做类比的话,多路复用模型相当于收到通知后到店自取,异步IO模型就是送餐上门。相对来说,异步IO是更加理想的IO模型。
异步IO:相当于送餐上门
Java7之后开始支持异步IO。相比于IO多路复用模型,异步IO并不十分常用,因为目前操作系统(尤其Linux)对异步IO的支持还不是特别好。高性能并发服务程序(比如Netty),一般都使用IO多路复用模型+多线程任务处理的架构。
我会持续更新关于物联网、云原生以及数字科技方面的文章,用简单的语言描述复杂的技术,也会偶尔发表一下对IT产业的看法,欢迎大家关注,谢谢。