学习python asyncio

异步IO即发起一个IO操作不用等它结束,可以继续做其他事情,当他结束时,会收到通知。我们这里之所以要使用异步IO是因为我们的软件执行过程中很多时候都是在不断通过网络读取数据,因为从网络获取数据的过程延迟相当大,这些等待数据时间我们不能让CPU闲着,浪费资源,导致利用率下降,而且我们的程序无论是Remote Listen还是Local Slave都不只是从一边读取数据,比如Local Slave,既要从Remote Listen读取数据,又要从Local Server读取数据,因此我们这里采用异步IO来实现。

Asyncio是一种并发的方式,Asyncio包中包括:1.对于各种系统都有特定实现的时间循环event loop;2.Tasks和coroutines用来按顺序方式写入并入代码;3.Transports和protocols(基于回调callback的API);4.streams(基于协程coroutine的API);5.同步原语;6.任务队列;7.模拟concurrent.future中的Future类,是适合与EventLoop一起使用的Future类;

Eventloop事件循环:事件循环是asyncio核心执行部分,eventloop会对方在事件循环中协程进行调度,比如某个协程“暂停”或则在等待一个IO操作时,eventloop就会开始调度,决定接下来执行哪个协程。EventLoop提供了许多种机制:1.注册、执行和取消延迟调用(异步函数);2.创建客户端与服务端传输用于通信;3.创建子程序和通道跟其他的程序进行通信;4.指定函数的调用到线程池。创建一个事件循环用get_event_loop()函数就可以,之后就是调用EventLoop的相关函数,将协程放入事件循环和运行事件循环。

Coroutine协程:Python中异步函数被称作协程,在函数前面使用async关键字就可以创建一个协程,使用@asyncio.coroutine装饰器也可以,在基于协程的API中许多函数也会创建协程,“asyncio.sleep()”也是一个协程。可以使用“asyncio.iscoroutine()”来判断是否是协程。协程可以做的:1.挂起等待future完成,然后返回future的结果“result=await future”;2.等待另一个协程返回结果“result=await coroutine”;3.产生一个返回值给正在等他的协程;4.引发一个异常给正在等待的协程。调用协程函数,除非调度安排它的执行,否则协程不会开始运行,有两种基本的运行协程函数的方法,在其他正在运行的协程中await coroutine或者yield from coroutine;或则用ensure_future()函数或则使用AbstractEventLoop.create_task()方法来运行协程,总之携程只可以在事件循环运行时开始运行,当我们把协程对象交给eventloop,协程对象随后就会在时间循环中运行。

Asyncio中有两种API:callback based API和coroutine based API。
1. Tansport and protocols(callback based API):Transports被asyncio提供用来抽象不同通信信道,不需要自己实例化,可以通过AbstractEventLoop方法来创建transport,例如”AbstractEventLoop.create_connection()”就会在连接建立时,创建一个transport来代表它。当通信信道建立,一个传输transport总是会有个protocol实例,这个protocol会调用transport的不同方法为了不同的目的。Base Transport用许多方法,在下面的代码中我们都会用到,特别是get_extra_info(),另外transport为了只读和只写的通信transport还有ReadTransport和WriteTransport,以及数据报传输DatagramTransport和用来和子进程通信的BaseSubprocessTransport。Asyncio中的Protocols有许多类来实现网络协议,他们和transport一起使用,protocol会解析输入的数据和请求输出的数据,protocol的方法method是回调callback的,当特定事件发生时他们就会被调用,不需要我们自己调用。Protocol还有两个子类:用于UDP通信的数据包协议DatagramProtocol和用来和子进程通信的SubprocessProtocol。Asyncio为protocol准备了很多的回调callback函数,比如:connection_made()、connection_lost()、data_received()、pause_writing()……
2. Streams(coroutine based API):基于协程coroutine的API中许多高级的函数只是为了方便使用对之前函数的封装,可以重写他们的代码,例如open_connection()、start_server()…open_connection()是一个协程,用来连接指定的IP地址,会返回一个StreamReader和一个StramWriter的实例。Start_server(client_connected_cb,host,port,)会建立一个socket服务端,对于每个客户端的链接都会有回调,client_connected_cb是一个回调函数或一个协程函数,它的两个参数分别是客户端的reader和writer,基于corotine的API中为streamReader和streamWriter提供了许多方法。
3. 两个API中,虽然分为基于回调和基于协程的,但是也有很多两种方法一起使用的地方,基于协程的API一些函数方法也用到回调函数,例如:start_server、StreamReaderProtocol…协程croutine也可以在protocol中通过ensure_future()来调用,但不能保证顺序,protocol无法意识到协程在他们的方法中被创建所以不会等待他们,为了有一个可靠的执行顺序,yield from 和stream对象一起使用。
4. 协程和回调的优缺点在与,协程虽然使用十分方便但是协程函数的调用没有顺序,是交给eventloop来调度的,回调会出现回调地狱的问题,当回调多了,代码会像一层套一层,很不好理解,而且最重要的是异步回调当发生异常时,因为回调函数的执行栈与原函数分离了,导致外部无法抓住异常。

在LCX项目中我们采用基于协程的API,因为本来Remote和Local两边收发数据的时间就是不确定随机的,没有顺序,Remote listen随时都有可能收到Remote client要求发给local slave的数据,也随时都有可能收到Local slave转发过来的Localserver的数据,因此顺序是不一定的,用基于协程的方法可以解决,而基于回调的方法需要确定什么回调函数会在什么事件发生后调用等等这些顺序,跟程序的实际情况难以吻合。

猜你喜欢

转载自blog.csdn.net/Han_L/article/details/80962483