python并发编程之IO模型 同步 异步 阻塞 非阻塞

IO浅谈

首先web

咱们在谈及IO模型的时候,就必需要引入一个“操做系统”级别的调度者-系统内核(kernel),而阻塞非阻塞是跟进程/线程严密相关的,而进程/线程又是依赖于操做系统存在的,因此天然不能脱离操做系统来讨论阻塞非阻塞。同步/异步也是跟任务流相关的,因此要全面理解就必须考虑到并发的任务流,否则,确定很难举出恰当的例子的。编程

 

本文讨论的背景是Linux环境下的network IO。
本文最重要的参考文献是Richard Stevens的“UNIX® Network Programming Volume 1, Third Edition: The Sockets Networking ”,6.2节“I/O Models ”,Stevens在这节中详细说明了各类IO的特色和区别,若是英文够好的话,推荐直接阅读。Stevens的文风是有名的深刻浅出,因此不用担忧看不懂。本文中的流程图也是截取自参考文献。

网络

 

在网络并发编程中,通俗的讲IO分为两个东西:一、等待 ==> 二、数据搬迁并发

或者这样说:当IO发生时,会涉及到对象和步骤,当进行网络IO(Network IO)时候,必须涉及到两个系统对象,一个不可避免的就是产生这个IO的进程或者线程(process or thread),还有另一个老大级人物:系统内核(kernel),至于为什么要让老大出场处理IO,究其缘由就是,好比咱们在进行读操做的时候,进程或线程是没法直接跟硬件打交道,没法直接从硬盘中直接拿到咱们想要的数据,而是须要系统内核取帮咱们去取,它取须要过程,有过程咱们就得等待,系统内核取到了先会将数据放进它本身的内存空间中,咱们要取还得从它的内存空间把数据迁移到进程的内存空间,这才算真正地拿到数据。异步

通俗的讲就是:socket

当一个read操做发生时,会经历两个阶段:async

1)等待数据准备(Waiting for the data to be ready)函数

2)将数据从内核拷贝到进程(线程所在的)的内存中(Copying the data from the kernel to the process or thread)性能

四个概念

同步 synchronous

一个进程或线程在提交任务或发出调用后,要等待返回的最终结果后才算执行完毕,而后才会继续执行下一步操做spa

所谓同步,就是没获得最终结果以前,就不会继续下一步操做。
这就比如单行道过收费站同样,一个接着一个,只有上一个结束收费下一辆车才能开始进入站窗口交费。每辆车就至关与一个进程或线程。
其实绝大多数的函数都是同步调用,咱们都是拿到结果以后在进行下一步代码操做。

而咱们在谈及同步和异步的时候,特指那些须要其余部件协做或者须要必定时间完成的任务。

异步 asynchronous

异步的概念与同步相对,当一个进程或线程发起一个异步功能调用或者说提交任务方式是异步提交的时,不会当即获得结果。它会继续执行下一步代码。而异步功能完成后,经过状态、通知或回调函数来通知调用者。

若是是经过状态在通知,那么调用者须要每隔一段时间检查一次结果是否返回,效率很低。

若是使用通知或者回调的方式,则进程或线程就不须要再作额外的操做,效率很高。

阻塞 blocking

首先阻塞是一种状态,一种进程或者线程的状态,不要把阻塞与同步混淆。

也就是说当进程或线程发起调用或提交任务以后,结果返回以前,好比遇到IO操做,则调用者(该进程或线程)就会被挂起,而后只有等到有结果后才会再将阻塞的线程激活。

非阻塞 non-blocking

非阻塞与阻塞相反,就算不能当即获得结果也不会将其挂起,而是会继续执行下一步。

四种常见IO模型 

 IO模型主要分五种

    blocking IO    阻塞IO
    nonblocking IO  非阻塞IO
    IO multiplexing  复用型IO
    signal driven IO  信号驱动型IO
    asynchronous IO  异步IO

他们直接的区别点在于过程、进程(或线程)的状态、发起调用方式

首先最多见的IO模型:blocking IO

blocking IO

默认状况下,全部的socket都是阻塞IO,一个典型的read操做流程

non-blocking IO

设置socket可使其变为非阻塞,当对一个非阻塞的socket执行读操做的时候,流程会不同:

从图中能够看出,当用户进程发出read操做时,若是kernel中的数据尚未准备好,那么它并不会block用户进程,
而是马上返回一个error。从用户进程角度讲 ,它发起一个read操做后,并不须要等待,而是立刻就获得了一个结果。
用户进程判断结果是一个error时,它就知道数据尚未准备好,因而它能够再次发送read操做。一旦kernel中的数据准备好了,
而且又再次收到了用户进程的system call,那么它立刻就将数据拷贝到了用户内存,而后返回。 因此,用户进程实际上是须要不断的主动询问kernel数据好了没有。

IO multiplexing

IO multiplexing这个词可能有点陌生,可是若是我说select,epoll,大概就都能明白了。有些地方也称这种IO方式为event driven IO。咱们都知道,select/epoll的好处就在于单个process就能够同时处理多个网络链接的IO。它的基本原理就是select/epoll这个function会不断的轮询所负责的全部socket,当某个socket有数据到达了,就通知用户进程。它的流程如图:

当用户进程调用了select,那么整个进程会被block,而同时,kernel会“监视”全部select负责的socket,当任何一个socket中的数据准备好了,select就会返回。这个时候用户进程再调用read操做,将数据从kernel拷贝到用户进程。
这个图和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。

Asynchronous I/O

 

用户进程发起read操做以后,马上就能够开始去作其它的事。而另外一方面,从kernel的角度,当它受到一个asynchronous read以后,首先它会马上返回,因此不会对用户进程产生任何block。而后,kernel会等待数据准备完成,而后将数据拷贝到用户内存,当这一切都完成以后,kernel会给用户进程发送一个signal,告诉它read操做完成了。

小结

 blocking和non-blocking的区别

调用blocking IO会一直block住对应的进程直到操做完成,而non-blocking IO在内核准备数据这个阶段,进程是不会非阻塞状态。

synchronous IO和asynchronous IO的区别

同步IO在进行IO操做的时候会将进程阻塞,而异步IO却不会。

因此blocking IO,non-blocking IO,IO multiplexing都属于synchronous IO,而asynchronous IO则不同,当进程发起IO 操做以后,就直接返回不再理睬了,直到kernel发送一个信号,告诉进程说IO完成。在这整个过程当中,进程彻底没有被block。

 最后各个IO Model的比较如图所示:

 

 

 

 

 

 

 

 

———————————————— 
版权声明:本文为CSDN博主「historyasamirror」的原创文章,遵循CC 4.0 by-sa版权协议,转载请附上原文出处连接及本声明。
原文连接:https://blog.csdn.net/historyasamirror/article/details/5778378

相关文章
相关标签/搜索