UNIX网络编程1 理解同步、阻塞、非阻塞、异步网络I/O

本文侧重的是网络I/O,如blocking、non-blocking、I/O multiplexing(event driven I/O)、signal-driven(不常用)、 asynchronous这几种UNIX网络编程中提到的模型。
wiki中的Asynchronous I/O认为,asynchronous I/O和non-blocking I/O是一个意思,都是指一种I/O处理形式:在传输完成之前,允许用户进程去继续执行其他的事情。synchronous I/O或blocking I/O在通信进行时会阻塞用户程序的执行,等待I/O操作的完成。
stackoverflow中有人这样描述:blocking和synchronous的意思是,当调用API时,当前线程会被挂起,直到有结果返回;Non-blocking则是如果结果还没有准备好,调用的API立马返回一个error并且不再做任何事情。因此你必须用一些相关方法来查询该API是否准备好被调用(最好是以一种高效的方式模仿等待,而避免在一个紧凑的循环中手动polling轮询);Asynchronous的意思是调用的API总是立即返回,而且后台还在完成请求工作,因此必定有一些方法来获得结果,这和non-blocking是有区别的地方。
还有一种说法,同步和异步是针对应用程序和内核交互而言的,同步过程中进程触发IO操作并等待或者轮询的去查看IO操作是否完成。异步过程中进程触发IO操作以后,直接返回,做自己的事情,IO交给内核来处理,完成后内核通知进程IO完成。
综合各种说法,我们可以得出结论:Asynchronous I/O异步在整个I/O过程发出请求的用户进程都不阻塞,所以肯定是非阻塞。阻塞和非阻塞可以理解为进行一个调用时,这个调用能不能立即返回,non-blocking模式下recvfrom可以立即返回,所以在内核检查数据未准备好时这个调用是非阻塞的,多路复用(select、poll、epoll)对每个socket来说都设置成non-blocking,select函数本身通过指定timeout是0秒0毫秒变成非阻塞函数,而拷贝数据阶段大家都是阻塞的,用户应用进程和内核是同步的。所以,同步模型肯定有阻塞的过程(拷贝数据阶段),也有可能有非阻塞调用。于是乎,select就变成了同步非阻塞模型。

对于一个网络I/O比如read,涉及到用户空间的应用程序进程或线程和系统内核,当一个read操作发生时,它会经历两个阶段:
1)等待数据准备
2)将数据从内核拷贝到进程中
按照这两个阶段,以recvfrom对比blocking、non-blocking、multiplexing、asynchronous,详见UNIX网络编程卷1第6章。
blocking I/O:
在linux中,默认情况下所有的socket都是blocking,一个典型的读操作流程大概是这样,不管是在kernel等待数据准备阶段,还是在从内核复制数据到用户进程阶段,应用程序都是blocking:


non-blocking I/O:
在linux下,可以通过设置socket使其变为non-blocking,对一个non-blocking socket执行读操作,当kernel还在等待数据准备好时,用户应用程序调用recvfrom系统调用则会立即返回一个错误EWOULDBLOCK,可以循环调用recvfrom(轮询)不断查询内核看数据是否准备好,这个阶段用户应用进程是non-blocking的:

I/O multiplexing:
在linux中,调用select或poll系统调用,用户应用程序就在这两个系统调用上阻塞blocking,而不是阻塞于真正的I/O系统调用。应用程序阻塞于select调用,等待socket可读,当select返回socket可读条件时,应用程序就可以调用recvfrom将数据报从内核拷贝到应用空间中。

和blocking I/O对比,其实在内核等待数据准备阶段和从内核拷贝数据到用户空间阶段,应用程序都blocking了,只不过multiplexing在第一个阶段是阻塞与select系统调用。在处理单个连接时,由于multiplexing使用了两个系统调用,实际上比直接blocking I/O性能还差,但multiplexing的目的并不是处理单个连接,二是同时处理多个连接(连接数较少时,使用多线程+直接blocking延迟可能更低)。
在multiplexing中,对于每一个socket一般都设置成non-blocking(即select再往下调用,询问单个socket的数据是否准备好时是non-blocking的),但用户应用程序是一直blocking的,只不过不是被I/O系统调用所block。

asynchronous I/O:
用户应用程序发起aio_read操作之后,就立即可以去做其他的事情。在kernel中,当它收到一个aio_read后,首先会立刻返回所以不会对用户进程产生任何block。然后kernel等待数据准备完成,然后将数据拷贝到用户空间,当这两个阶段都完成后,kernel会给应用进程发送一个siignal,告诉它read操作完成了。


比较四种模型,blocking、non-block、multiplexing、signal(未讨论)主要区别是在第一阶段,第二阶段基本相同,在数据从内核拷贝到调用者的缓冲区时,进程阻塞于recvfrom调用。而异步I/O模型处理的两个阶段都不同于前四个模型。来源于UNIX网络编程第6张图6.6:

我们认为,blocking、non-blocking、multiplexing(event-driven)、signal-driven I/O模型和asynchronous是有明显区别的,即在I/O操作第二阶段会阻塞,所以认为这四种都是同步I/O模型。
另外Posix.1定义同步和异步I/O为:
  • 同步I/O操作引起请求进程阻塞,直到I/O操作完成。non-blocking I/O的recvfrom调用在数据没准备好时立即返回这是non-blocking的效果,对于select/poll也一样,但是在拷贝数据阶段都不得不阻塞用户进程。
  • 异步I/O操作不引起请求进程阻塞,调用aio_read后立即返回,请求进程可以去干别的事情,内核完成I/O操作后向请求进程递交在aio_read中指定的信号,用户应用程序还得有信号处理程序来另外处理信号从而接收数据报。
参考:
《UNIX网络编程第1卷 第6章》
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章