同步(synchronous) IO和异步(asynchronous) IO,阻塞(blocking) IO和非阻塞(non-blocking)IO分别是什么,到底有什么区别?我们以Linux环境下的network IO来讨论。
对于一个network IO (以read举例),它会涉及到两个系统对象,一个是调用这个IO的process (or thread),另一个就是系统内核(kernel)。当一个read操作发生时,它会经历两个阶段:
1 等待数据准备 (Waiting for the data to be ready)
2 将数据从内核拷贝到进程中 (Copying the data from the kernel to the process)
Richard Stevens的“UNIX® Network Programming Volume 1, Third Edition: The Sockets Networking ”,6.2节“I/O Models ”有以下5种: blocking IO; nonblocking IO; IO multiplexing; signal driven IO; asynchronous IO。其中signal driven IO不常见,以下分析4中模型。然后再区分IO的同步与异步,阻塞与非阻塞。
I/O Models
1.阻塞式I/O模型 blocking IO
在linux中,默认情况下所有的socket都是blocking
当用户进程调用了recvfrom这个系统调用,kernel就开始了IO的第一个阶段:准备数据;对于network io来说,很多时候数据在一开始还没有到达(比如,还没有收到一个完整的UDP包),这个时候kernel就要等待足够的数据到来。这时在用户进程这边,整个进程挂起,被阻塞。kernel一直等到数据准备好了,它就会将数据从kernel中拷贝到用户内存,然后kernel返回结果,用户进程才解除block的状态,重新运行起来。
所以,blocking IO的特点就是在IO执行的两个阶段都被block了。
2.非阻塞I/O模型 non-blocking IO
linux下,可以通过设置socket使其变为non-blocking。
当用户进程调用了recvfrom这个系统调用,kernel就开始了IO的第一个阶段:准备数据;对于network io来说,如果kernel中的数据还没有准备好,那么它并不会block用户进程,而是立刻返回一个error。从用户进程角度讲 ,它发起一个read操作后,并不需要等待,而是马上就得到了一个结果。用户进程判断结果是一个error时,它就知道数据还没有准备好,于是它可以再次发送read操作。一旦kernel中的数据准备好了,并且又再次收到了用户进程的system call,那么它马上就将数据拷贝到了用户内存,然后返回。所以,用户进程没有被挂起,可以干些别的,但是需要不断的主动询问kernel数据好了没有,直到准备好,发起一个系统调用。但是在第二个阶段仍然是block的。
3.I/O复用模型 IO multiplexing
I/O复用最常见的就是select和epoll,有些地方也称这种IO方式为event driven IO。select/epoll的好处就在于单个process就可以同时处理多个网络连接的IO。它的基本原理就是select/epoll这个function会不断的轮询所负责的所有socket,当某个socket有数据到达了,就通知用户进程。
当用户进程调用了select,那么整个进程会被block,不能干别的,而同时,kernel会“监视”所有select负责的socket,当任何一个socket中的数据准备好了,select就会返回。这个时候用户进程再调用read操作,将数据从kernel拷贝到用户进程。这个和阻塞式I/O模型 blocking IO的区别在于这里需要使用两个system call (select 和 recvfrom),而blocking IO只调用了一个system call (recvfrom)。但是,用select的优势在于它可以同时处理多个connection。(多说一句。所以,如果处理的连接数不是很高的话,使用select/epoll的web server不一定比使用multi-threading + blocking IO的web server性能更好,可能延迟还更大。select/epoll的优势并不是对于单个连接能处理得更快,而是在于能处理更多的连接。)
在IO multiplexing Model中,实际中,对于每一个socket,一般都设置成为non-blocking,但是,如上图所示,整个用户的process其实是一直被block的。只不过process是被select这个函数block,而不是被socket IO给block。
4.异步I/O模型 Asynchronous I/O
用户进程发起read操作之后,立刻就可以开始去做其它的事。而另一方面,从kernel的角度,当它受到一个asynchronous read之后,首先它会立刻返回,所以不会对用户进程产生任何block。然后,kernel会等待数据准备完成,然后将数据拷贝到用户内存,当这一切都完成之后,kernel会给用户进程发送一个signal,告诉它read操作完成了。
四者的区别:
blocking vs non-blocking:调用blocking IO会一直block住对应的进程直到操作完成,准备阶段和拷贝阶段都被blocking,而non-blocking IO在kernel还准备数据的情况下会立刻返回,只是在拷贝阶段blocking。
synchronous IO和asynchronous IO:
首先看定义:(简单来说:同步I/O:导致请求进程阻塞,直到I/O操作完成;异步I/O: 不导致请求进程阻塞。)
A synchronous I/O operation causes the requesting process to be blocked until that I/O operationcompletes; An asynchronous I/O operation does not cause the requesting process to be blocked;
blocking IO,non-blocking IO,IO multiplexing都属于synchronous IO。
有人可能会说,non-blocking IO并没有被block啊。这里有个非常“狡猾”的地方,定义中所指的”IO operation”是指真实的IO操作,就是例子中的recvfrom这个system call。non-blocking IO在执行recvfrom这个system call的时候,如果kernel的数据没有准备好,这时候不会block进程。但是,当kernel中数据准备好的时候,recvfrom会将数据从kernel拷贝到用户内存中,这个时候进程是被block了,在这段时间内,进程是被block的。而asynchronous IO则不一样,当进程发起IO 操作之后,就直接返回再也不理睬了,直到kernel发送一个信号,告诉进程说IO完成。在这整个过程中,进程完全没有被block。
通俗的例子:
3个人排队去买包子,甲买肉馅馒头,乙买素馅馒头,丙买白馒头。
如果是阻塞式I/O模型,按照先后到来顺序处理甲乙丙,甲到来时候,店铺老板说我在做啊,你就在这等着我做完,哪里也不要去,肉馅馒头做好后给甲,甲可以走了,接着处理乙,乙处理完,再处理丙。
如果是非阻塞式I/O模型,假设还是甲乙丙顺序来,甲询问说我要肉馅馒头,老板说还没有做好,这时侯甲就离开搞其他的事情了,乙到了,询问说我要素馅馒头,老板说还没有做好,这时侯乙就离开搞其他的事情了,丙到了,询问说我要白馒头,老板说还没有做好,这时侯丙就离开搞其他的事情了。只不过甲乙丙还会时不时来询问我要的好了没有,假设白馒头非常好做,某个时刻老板做好了,正巧碰到丙又来询问,老板此刻说你的好了,此时丙哪里也不要去了,什么也不要做了,等着老板把白馒头返回给他。此后甲和乙时不时还来询问我要的好了没有。。可见这时候甲乙丙并不需要一直等待,可以做其他事情,同时并不一定是甲先来一定会被先处理。
如果是I/O复用模型,假设还是甲乙丙顺序来,然后甲乙丙会把自己的需求告诉店小二,然后店小二负责去询问老板肉馅馒头,素馅馒头,白馒头做好了没有。如果在I/O复用模型中,socket没有non-blocking时候,甲乙丙不能走,其他事情也不能做,干等着。店小二通知说素馅馒头好了,这时候乙去见老板拿自己所需要的。我们看到这时候并不是按甲乙丙顺序处理的,虽然甲乙丙被店小二阻塞了,但给人感觉是“并发”,哪个先准备好,先处理哪个。
如果是异步I/O模型,假设还是甲乙丙顺序来,店老板立即给甲乙丙返回一个纸条“好的”,甲乙丙各自散去了,该干嘛就干嘛,这时候店老板做馒头,无论是先做好了甲还是乙丙,就通知他们并把相应的馒头交给他们。甲乙丙也不需要时不时的来询问,更不需要去等待啦。
参考:
http://blog.csdn.net/historyasamirror/article/details/5778378