Contents
  1. 1. 起因
  2. 2. Python异步的多种实现
  3. 3. 了解异步基础
  4. 4. 浅析Python异步实现
  5. 5. 深入asyncio了解Python异步
    1. 5.1. 引用

起因

异步的出现主要是单线程的io等待,由于任务大部分是io处于等待,假如让一个线程工作,所有任务按照流水线形式执行,假如一个请求需要1秒,五个请求需要五秒,那么如果能让他们同时运行的话,那么速度就能增加五倍

如何让五个任务同时进行有两种方法

  1. 多线程
  2. 异步

调试过过多线程的人都知道,线程就是从头到复制主线程一遍,开多个线程不仅成本高,而且调试成本高,异步就不一样呢,你可以把它当做一个单线程来进行编程,而且比多线程更加高效

Python异步的多种实现

Python实现异步的框架有很多,但是核心思想大概是基于下面两种方式

  • twister
  • gevent

twister思想是将异步操作封装起来,通过回调的方式来操作,我们看scrapy里面中间请求的实现就是twister方式

scrapy.Request(url='xxx', callback=func)

通过传递封装的request,当框架帮我们请求完后,会通过callback进行回调,如果你的请求很简单那还好,只需要回调一次就可以,假如你的请求较复杂,那么你就会进入回调地狱(callback hell)

而且你还要写处理各种回调产生的异常,你可以看看scrapy中间件的实现就知道scrapy的异常处理有多繁琐了。但是中间间的存在的确让我们代码模块话更加容易,这里暂且不谈。

twister这种回调比较反人类,它必须依赖背后的核心进行调度,离开了背后核心的支持,这个根本跑不起来,而且由于它依赖回调来进行后续步骤处理,所以我们的代码必须被切分为不同的部分,假如我们不知道背后的核心如何回调函数或者约束,我们根本不知道这两个函数是有关联的

这种编程方式比较有利于模块话开发,但是对于我们熟悉顺序编程来看,这种回调方式显然是一场噩梦,相比于twister这种回调方式,gevent采用的是绿色协程的方式进行回调。

PEP-380定义了yield from的语句,Python3.3开始使用,为了区别协程和生成器,Python3.5开始使用await代替yield from,这样协程就有了一个专门的方法来声明(awaitasync),后者用来标记异步函数

协程之所以能够在异步中大方光彩,其中很大一部分就是协程天生就是异步的,理解协程我们可以从一个简单的生成器与普通函数来对比

a = (x for x in range(10))
b = [x for x in range(10))

我们来看这样一个生成器a,一般我们来用这个生成器必须加 for循环才能得到里面的值,假如我们尝试使用a.send(None),我们会发现,我们依次从返回值得到了b里面的序列

就是这么一个send与接受的功能让我们实现了一种”绿色“回调,就是协程这个性质让他写异步变得更加顺理成章了,而且相比twister回调,协程的回调更为彻底,它把”自己”包装起来全部回调回去了。

了解异步基础

前面简单的聊了协程的性质,现在谈谈异步存在的基础,异步的存在最关键的在于等待,为了了解这个等待意思和后面解读asycio库,我们先使用selectors (Python3对select的封装)来做个演示

import selectors
sel = selectors.DefaultSelector()

声明一个select对象sel,现在我们要调用这个核心函数

sel.select(10)

这个10是代表timeout的时长,也就是最长等待时间,10秒之后我们发现,这个结果返回了一个空列表,这是显而易见的,我们并没有指明让它等待什么

selectors这个库的功能非常好理解,类比寄信,你如果想等别人回信,假如你没有寄出去你自己的信,你一直在邮箱那等,除了等到你不想等,否则你是收不到你的回信的,所以这个库的核心在于,“寄信”(register)和等信(select),然后自己选择处理信件

import selectors
import socket

sel = selectors.DefaultSelector()

def accept(sock, mask):
    conn, addr = sock.accept() 
    print('accepted', conn, 'from', addr)

sock = socket.socket()
sock.bind(('localhost', 8000))
sock.listen(100)
sock.setblocking(False)
sel.register(sock, selectors.EVENT_READ, accept)

while True:
    events = sel.select()
    for key, mask in events:
        callback = key.data
        callback(key.fileobj, mask)

这个程序最关键的地方在于sel.registersel.selectcallback那里,前者是注册函数,后面是等待,最后就是回调

上面就是twister式最简单的回调,你可以看到,为了得到连接sock的连接,我们必须把处理注册到等待中去,但是这只是得到sock连接,为了成功建立一个TCP连接,我们还得进行三次握手,还得处理每次回调时的错误

而且你可以看到回调函数与核心驱动select.select()耦合度非常高,我们必须完全了解系统如何回调,处理一件事被回调分割成一段一段

接下来我们来看看基于geventasyncio实现

async def wget(host):
    connect = asyncio.open_connection(host, 80)
    reader, writer = await connect
    header = 'GET / HTTP/1.0\r\nHost: %s\r\n\r\n' % host
    writer.write(header.encode('utf-8'))
    await writer.drain()
    while True:
        line = await reader.readline()
        if line == b'\r\n':
            break
    writer.close()

我们成功的用一个函数描绘了建立一次连接并且进行通信的过程,假如你懂一点asyncio,你就会发现它与twister回调的不同,使用await关键字把函数挂起,然后等待回调,根据回调接着进行下面的操作,我们成功的用同步的语句把异步写出来,而且是使用Python的原生实现,所以当asyncio出来的时候Guido(Python之父)是多么自豪,你可以看下面引用 Tulip: Async I/O for Python 3演讲的视频

浅析Python异步实现

前面我们知道了异步的基础就是等待,那么Guido是如何在协程的帮助下将异步实现出来的呢,接下来我们就简单的谈一下这个实现基础

我们先将上面twister改成gevent方式的

sel = selectors.DefaultSelector()


@asyncio.coroutine
def get_connection(sock):
    sel.register(sock, selectors.EVENT_READ)
    yield True


async def create_connection():
    sock = socket.socket()
    sock.bind(('localhost', 8000))
    sock.listen(100)
    sock.setblocking(False)
    await get_connection(sock)
    conn, addr = sock.accept()
    print('accepted', conn, 'from', addr)


event = create_connection()
event.send(None)
events = sel.select(100)
for key, mask in events:
    try:
        event.send(None)
    except StopIteration:
        pass

我们稍稍修改一下上面的twister函数,我们创建一个get_connection函数把sock绑定到我们的sel上面,然后回调一个True,当然这个回调没有处理异常什么的,然后我们将得到的协程向其发送一个None让它启动,这时候你在在另外一个ipython客户端执行

import socket
socket.socket()..connect(('localhost', 8000))

然后你就会发现在主线程里面打印出来客户端的连接信息

通过这个小例子我们知道,实现异步要解决的问题就是一个公用注册器(能够注册所以的io等待),一个容器(能够存贮所以的协程),一个核心能够一直执行等待回调和处理回调(多个协程)

深入asyncio了解Python异步

通过上面我们简单的知道了,如何通过协程与select合作完成异步操作,然而我们上面写的只是最最最基本的实现,接下来我们来深入asyncio源码了解如何让异步变得更加简单

引用

Python异步并发框架

Python 中的异步编程:Asyncio
Tulip: Async I/O for Python 3
【译】深入理解python3.4中Asyncio库与Node.js的异步IO机制

Contents
  1. 1. 起因
  2. 2. Python异步的多种实现
  3. 3. 了解异步基础
  4. 4. 浅析Python异步实现
  5. 5. 深入asyncio了解Python异步
    1. 5.1. 引用