理解同步异步与阻塞非阻塞

转载自:http://blog.csdn.net/initphp/article/details/42011845
              http://www.aboutyun.com/thread-16941-1-1.html

阻塞式IO (blocking IO)

       在linux中,默认情况下所有的socket都是blocking,一个典型的读操作流程大概是这样:
理解同步异步与阻塞非阻塞_第1张图片
 
       当用户进程调用了recvfrom这个系统调用,kernel就开始了IO的第一个阶段:准备数据。对于network io来说,很多时候数据在一开始还没有到达(比如,还没有收到一个完整的UDP包),这个时候kernel就要等待足够的数据到来。而在用户进程这边,整个进程会被阻塞。当kernel一直等到数据准备好了,它就会将数据从kernel中拷贝到用户内存,然后kernel返回结果,用户进程才解除 block的状态,重新运行起来。

       所以,blocking IO的特点就是在IO执行的两个阶段(等待数据和拷贝数据两个阶段)都被block了。


       第一次接触到的网络编程都是从listen()、send()、recv() 等接口开始的,这些接口都是阻塞型的。大部分的socket接口都是阻塞型的。所谓阻塞型接口是指系统调用(一般是IO接口)不返回调用结果并让当前线程一直阻塞,只有当该系统调用获得结果或者超时出错时才返回。实际上,除非特别指定,几乎所有的IO接口 ( 包括socket接口 ) 都是阻塞型的。这给网络编程带来了一个很大的问题,如在调用send()的同时,线程将被阻塞,在此期间,线程将无法执行任何运算或响应任何的网络请求。
       一个简单的改进方案是在服务器端使用多线程(或多进程)。多线程(或多进程)的目的是让每个连接都拥有独立的线程(或进程),这样任何一个连接的阻塞都不会影响其他的连接。

无阻塞式IO (nonblocking IO)

       Linux下,可以通过设置socket使其变为non-blocking。当对一个non-blocking socket执行读操作时,流程是这个样子:
理解同步异步与阻塞非阻塞_第2张图片
       从图中可以看出,当用户进程发出read操作时,如果kernel中的数据还没有准备好,那么它并不会block用户进程,而是立刻返回一个error。 从用户进程角度讲 ,它发起一个read操作后,并不需要等待,而是马上就得到了一个结果。用户进程判断结果是一个error时,它就知道数据还没有准备好,于是它可以再次 发送read操作。一旦kernel中的数据准备好了,并且又再次收到了用户进程的system call,那么它马上就将数据拷贝到了用户内存,然后返回。
       所以,在非阻塞式IO中,用户进程其实是需要不断的主动询问kernel数据准备好了没有。

异步IO (asynchronous IO)

       Linux下的asynchronous IO其实用得不多,从内核2.6版本才开始引入。先看一下它的流程:
理解同步异步与阻塞非阻塞_第3张图片
       用户进程发起read操作之后,立刻就可以开始去做其它的事。而另一方面,从kernel的角度,当它受到一个asynchronous read之后,首先它会立刻返回,所以不会对用户进程产生任何block。然后,kernel会等待数据准备完成,然后将数据拷贝到用户内存,当这一切都 完成之后,kernel会给用户进程发送一个signal,告诉它read操作完成了。

       那么blocking和non-blocking的区别在哪,synchronous IO和asynchronous IO的区别在哪?
       调用blocking IO会一直block住对应的进程直到操作完成,而non-blocking IO在kernel还在准备数据的情况下会立刻返回。
       在说明synchronous IO和asynchronous IO的区别之前,需要先给出两者的定义。Stevens给出的定义(其实是POSIX的定义)是这样子的:
          * A synchronous I/O operation causes the requesting process to be blocked until that I/O operation completes;
          * An asynchronous I/O operation does not cause the requesting process to be blocked;
       两者的区别就在于synchronous IO做”IO operation”的时候会将process阻塞。按照这个定义,之前所述的blocking IO,non-blocking IO都属于synchronous IO。有人可能会说,non-blocking IO并没有被block啊。这里有个非常“狡猾”的地方,定义中所指的”IO operation”是指真实的IO操作,就是例子中的recvfrom这个系统调用。non-blocking IO在执行recvfrom这个系统调用的时候,如果kernel的数据没有准备好,这时候不会block进程。但是当kernel中数据准备好的时候,recvfrom会将数据从kernel拷贝到用户内存中,这个时候进程是被block了,在这段时间内进程是被block的。而asynchronous IO则不一样,当进程发起IO操作之后,就直接返回再也不理睬了,直到kernel发送一个信号,告诉进程说IO完成。在这整个过程中,进程完全没有被block。



       线程在执行中如果遇到磁盘读写或网络通信(统称IO操作),通常要耗费较长的时间,这时操作系统会剥夺此线程 的CPU控制权,使其暂停执行,同时将资源让给其他的工作线程,这种线程调度方式称为阻塞。当IO操作完毕时,操作系统将这个线程的阻塞状态解除,恢复其 对CPU的控制权,令其继续执行。这种IO模式就是同步式IO或阻塞式IO。

       相应地,异步IO即非阻塞式IO则针对所有IO操作不采用阻塞的策略。当线程遇到IO操作时,不会以阻塞的方式等待IO操作的完成或数据的返回,而只是将IO请求发送给操作系统,继续执行下一条语句。当操作系统完成IO操作时,以事件的形式通知执行IO操作的线程,线程会在特定时候处理这个事件。为了处理异步IO,线程必须有事件循环,不断地检查有没有未处理的事件,依次予以处理。

       阻塞模式下,一个线程只能处理一项任务,要想提高吞吐量必须通过多线程。而非阻塞模式下,一个线程永远在执行计算操作,这个线程所使用的CPU核心利用率永远是100%,IO以事件的方式通知。在阻塞模式下,多线程往往能提高系统吞吐量,因为一个线程阻塞时还有其他线程在工作,多线程可以让CPU资源不被阻塞中的线程浪费。而在非阻塞模式下,线程不会被IO阻塞,永远在利用CPU。 多线程带来的好处仅仅是在多核CPU的情况下利用更多的核。

       单线程事件驱动的异步式IO比传统的多线程阻塞式IO究竟好在哪里呢?简而言之,异步式IO就是少了多线程的开销。对操作系统来说,创建一个线程的代价比较昂贵,需要给它分配内存、列入调度,同时在线程切换的时候还要执行内存换页,CPU的缓存被清空,切换回来的时候还要重新从内存中读取信息,破坏了数据的局部性。



你可能感兴趣的:(理解同步异步与阻塞非阻塞)