常见I/O模型
- 同步阻塞IO(Blocking IO): 传统的IO模型
- 同步非阻塞IO(Non-blocking IO): 默认常见的socket都是阻塞的,非阻塞IO要求socket被设置成NONBLOCK
- IO多路复用(IO Multiplexing): 即经典的Reactor设计模式,也被称为异步阻塞IO,Java中的selector和linux中的epoll都是这种模型
- 异步IO(Asychronous IO): 即Proactor设计模式,也被称为异步非阻塞IO
同步与异步
同步: 同步是指用户线程发起IO请求后需要等待或者轮询内核IO操作,完成后才能继续执行。
异步: 异步是指用户线程发起IO请求后仍继续执行,当内核IO操作完成后回通知用户线程,或者调用用户线程注册的回调函数。
同步阻塞IO(BIO)
同步阻塞IO是最简单的IO模型,用户线程在内核进行IO操作时被阻塞。用户线程通过调用系统调用read发起IO读操作,由用户空间转到内核空间。内核等到数据包到达后,然后将接受的数据拷贝到用户空间,完成read操作。整个IO请求过程,用户线程都是被阻塞的,对CPU利用率不够。
同步非阻塞IO(NIO)
在同步基础上,将socket设置为NONBLOCK,这样用户线程可以在发起IO请求后立即返回。虽说可以立即返回,但并未读到任何数据,用户线程需要不断的发起IO请求,直到数据到达后才能真正读到数据,然后去处理。
整个IO请求中,虽然可以立即返回,但是因为是同步的,为了等到数据,需要不断的轮询、重复请求,消耗了大量的CPU资源。因此,这种模型很少使用,实际用处不大。
IO多路复用
不管是同步阻塞还是同步非阻塞,对系统性能的提升都是很小的。而通过复用可以使一个或一组线程(线程池)处理多个TCP连接。IO多路复用使用两个系统调用(select/poll/epoll和recvfrom),blocking IO只调用了recvfrom。select/poll/epoll核心是可以同时处理多个connection,而不是更快,所以连接数不高的话,性能不一定比多线程+阻塞IO好。
select是内核提供的多路分离函数,使用它可以避免同步非阻塞IO中轮询等待问题。
异步IO(AIO)
在IO多路复用模型中,事件循环文件句柄的状态事件通知给用户线程,由用户线程自行读取数据、处理数据。而异步IO中,当用户线程收到通知时候,数据已经被内核读取完毕,并放在了用户线程指定的缓冲区内,内核在IO完成后通知用户线程直接使用就行了。因此这种模型需要操作系统更强的支持,把read操作从用户线程转移到了内核。
相比于IO多路复用模型,异步IO并不十分常用,不少高性能并发服务程序使用IO多路复用+多线程任务处理的架构基本可以满足需求。不过最主要原因还是操作系统对异步IO的支持并非特别完善,更多的采用IO多路复用模拟异步IO方式(IO事件触发时不直接通知用户线程,而是将数据读写完毕后放到用户指定的缓冲区)。