IO的同步与异步、阻塞与非阻塞,Linux五种IO模型
- IO的同步与异步,阻塞与非阻塞
- 阻塞IO与非阻塞IO
- 同步IO与异步IO
- Linux五种IO模型
- BIO
- NIO
- IO多路复用
- 信号驱动IO
- AIO
IO的同步与异步,阻塞与非阻塞
我们有时会看到类似于同步阻塞式IO、同步非阻塞式IO、异步IO等这些名词。
大多数时候,同步就是阻塞的,异步就是非阻塞的,比如我们并发编程里面加锁进行同步,那么获取不到锁的就要阻塞等待。但是在IO里面,同步异步与阻塞非阻塞不是同一概念。
这里面的同步、异步、阻塞、非阻塞是什么意思呢?
阻塞IO与非阻塞IO
在IO里面,当数据还没有就绪时,当前线程是否需要等待数据到达,可以分为阻塞IO与非阻塞IO。阻塞式IO是指当数据未就绪时,当前线程阻塞等待数据就绪;非阻塞式IO是指当数据未就绪时,当前线程不会阻塞等待数据就绪。
这里的数据就绪是指,数据已经加载到操作系统内核空间。比如网络IO,当数据到达网卡缓冲区,会通过DMA(Direct Memory Access——直接内存访问)设备把数据拷贝到内核空间,此时代表数据就绪,也就是等待应用程序拷贝到用户空间。
同步IO与异步IO
当数据已经达到了之后,就需要把内核空间的数据拷贝到用户空间,把内核空间的数据拷贝到用户空间是通过CPU进行拷贝。
是否需要当前线程主动去把内核空间中的数据搬运到用户空间,这又分为同步IO和异步IO。需要当前线程主动拷贝内核空间的数据到用户空间的叫同步读取,不需要当前线程主动拷贝内核空间的数据到用户空间的叫异步读取。
那么,根据同步与异步、阻塞与非阻塞,就组合出了四种类型的IO:
首先这里要知道,异步阻塞IO是不存在的,因此阻塞式IO在数据未就绪前是阻塞等待的,直到数据就绪之后当前线程才解阻塞,而数据都已经到达了,那直接拷贝到用户空间就可以了,这时候弄一个异步读取就没有必要了。
Linux五种IO模型
BIO
BIO是同步阻塞式IO。以网络IO为例,当客户端没有发送数据时,当前线程会阻塞等待数据,当客户端发送的数据被网卡接收并被DMA设备拷贝到内核空间之后,当前线程把内核空间中的数据拷贝到用户空间,然后当前线程才从数据拷贝中返回。
那么我们看到的现象就是,当服务端调用read()函数时,服务端当前线程阻塞等待客户端发送数据,当客户端发送的数据被服务端接收并拷贝到内核空间之后,服务端的当前线程需要把数据拷贝到用户空间,然后服务端当前线程才从read()函数返回。
NIO
NIO是同步非阻塞IO,当客户端没有发送数据时,服务端线程不会阻塞等待,而是马上返回,当前线程需要不断的检测数据是否已经到达并被拷贝到内核空间。如果数据还没有准备好,那么read函数调用会马上返回一个error,用户线程可以再次发起read函数调用继续检测;如果数据已经在内核空间了,那么当前线程调用read函数就会拷贝内核空间的数据到用户空间,这个拷贝的过程是同步读取,是当前线程主动拷贝的,因此在线程拷贝数据到用户空间的这段时间,程序是不会往下执行的。
NIO相比BIO的不同在于当数据还未就绪时,使用NIO是不会导致当前线程阻塞的,但是当数据就绪时,NIO与BIO一样是由当前线程拷贝到用户空间的,因此当要拷贝内核空间的数据到用户空间时,NIO与BIO是一样的会卡住直到数据拷贝完毕。
IO多路复用
在Linux操作系统上面,IO多路复用通过Linux提供的select、poll、epoll等API实现。使用IO多路复用是会使得当前线程阻塞的,但是IO多路复用与阻塞IO不同,阻塞IO是当前线程阻塞监听一个socket文件描述符,而IO多路复用则是阻塞监听多个socket文件描述符。
当有一个或多个文件描述符有数据就绪时,则该线程解阻塞,可以操作这些有数据就绪的socket文件描述符读取数据。
信号驱动IO
要理解信号驱动IO,首先要理解什么是信号驱动。信号驱动是一种软中断,是在软件层面模拟硬件中断实现的一种中断机制。
首先当前线程向操作系统注册一个信号处理函数,操作系统内核保持该信号处理函数。
当对应的事件发生时,操作系统向当前线程发送一个SIGIO信号。然后触发一个软中断,当前线程切换到内核态,执行之前注册的信号处理函数。执行完毕之后,再切换回用户态,当前线程从中断处开始往下执行。
可见这是一种异步操作,再对应的事件没有发生时,当前线程可以继续往下执行,不会阻塞,当对应的事件发生时,才转而执行预先注册的函数。
信号驱动IO也是一种同步非阻塞IO。当前线程通过sigaction函数注册一个SIGIO信号处理函数,然后回马上返回不会阻塞。如果客户端发送的数据已被拷贝到内核空间,操作系统会发送一个SIGIO信号给当前线程,当前线程转而执行之前注册的SIGIO信号处理函数,里面会调用redvfrom读取内核空间的数据到用户空间,此时是由当前线程主动拷贝数据,因此从内核空间拷贝数据到用户空间期间当前线程阻塞,因此还是同步IO。
AIO
AIO是异步IO,异步IO是全程无阻塞的IO操作,性能最高。应用程序不需要阻塞等待数据到达,当数据到达时操作系统自动把内核空间的数据拷贝到用户空间,再通知当前线程处理数据。
首先用户线程调用aio_read函数并传递用户空间缓存区、缓冲区大小等参数,然后马上返回,不会阻塞当前线程。当数据已在内核空间中就绪时,操作系统内核自动把数据拷贝到指定的用户空间缓冲区。当操作系统内核拷贝数据到用户空间完成后,再发送一个信号通知用户线程处理数据。
但可惜的是,Linux操作系统对AIO的支持不是很好,Linux的AIO是基于IO多路复用做的封装,性能并没有很大的提升,因此使用的更多的是IO多路复用。但是Windows操作系统则在真正意义上实现了AIO,因此在Windows操作系统环境下使用AIO的话,可以在性能上得到很大的提升。