同步、异步、阻塞、非阻塞
网络IO
本篇主要涉及以下两个概念
- 同步、异步
- 阻塞、非阻塞
同步与异步
同步和异步是针对应用程序与内核交互而言的
- 同步指的是用户进程触发IO操作并等待或者轮询去查看IO操作是否准备就绪;
- 异步指的是用户进程触发IO操作以后便去干其他的的事情,而当IO操作已经完成的时候会得到IO完成的通知,这种通知一般是操作系统通过信号量实现。
阻塞和非阻塞
阻塞和非阻塞是针对进程在访问数据的时候,根据IO操作的就绪状态采取的不同方式
- 阻塞指的是当试图对文件描述符进行读写时,如果当时没有东西可读,或者暂时不可泄,程序就进入等待状态,知道有东西读或者写为止;
- 非阻塞指的是如果没有东西读写,读写函数立即返回,而不会等待。
IO模型的分类
应用程序像操作系统发出IO请求,这个过程包含两个阶段:
- 等待数据:数据可能来自其他应用程序或者网络,这部分是否阻塞取决于文件描述的属性,是阻塞还是非阻塞;
- 拷贝数据:将准备就绪的数据从内核态数据缓冲区拷贝到用户态的数据缓冲区。
如果上述两个过程是按照先后顺序进行,那么这种IO模型就是同步的,如果说把数据准备好了,放到了用户态的缓冲区,然后去干其他的事情,至于数据拷贝和等待数据全部交给内核去处理,这种就是异步的。异步的一个典型应用就是日志系统。
linux提供的同步读写函数read,write,recv,send, recvfrom, sendto。其中recv/send只能用于面向连接的服务,reccvfrom/sendto既能面向连接也能无连接。
同步阻塞IO
文件描述符设置为阻塞,之后使用read, write, recv, send, …
同步非阻塞IO
需要不断地进行轮询查看是否能读或者能写,然后再去进行数据拷贝。
IO复用
IO复用中select poll都是同步阻塞,epoll LT模式一般是同步阻塞,ET模式一般是同步非阻塞。
并发的CPU活跃,那么采用select或者poll,并发的CPU不活跃,那么采用epoll更好。
LT适用于传输数据少的应用,缓冲区一般不会满,搭配阻塞式使用,一般只需要读写一次,典型的如redis;
ET适用于传输数据多的应用,缓冲区很容易满,只能非阻塞式使用(否则最后一次读写一定阻塞),一般循环读写,典型的如nginx。
信号驱动
信号驱动模型在内核中注册一个SIGIO信号,当内核发现可读或者可写时回去调用相应的回调函数,这样用户态层面可以去处理其他事情。不过这个模型还是属于同步IO,因为具体回调函数中都是需要先来完成IO操作才可以去干其他事情。至于阻塞还是非阻塞和文件描述符的属性有关。
重点:内核通知可读或者可写
信号驱动模型看似很高效,但是如果io比较多的情况下会导致内核频繁发送信号,信号的开销也很大。
异步IO
aio_read或者aio_write仅仅只是操作用户态的事情,数据拷贝和数据就绪全部交给内核。引用程序发出一个读写指令后立即返回继续做其他事情,内核干完数据就绪和数据拷贝之后发出信号,会导致注册了该信号的回调函数被调用。显著特点是用户态发出读写命令后不干事了。异步一定是非阻塞的,否则最后一次读一定阻塞在信号处理函数里了,有点类似epoll的ET模式必须要是非阻塞的。
重点:内核通知写完或者读完。