Asyncio初见

写在之前

说实话,连我自己都已经快忘记这个博客了。之前因为系统重装的原因,这个博客的源码和所有文章都已经不在自己的电脑里面了。发现除了2016年的一篇博客之外,其它的博客都是15年之前了。惭愧惭愧,越来越懒了。

之所以最近又从新的想起了这个blog,起因是因为前几天因为搜格式化字符串漏洞的相关的资料的时候竟然在百度首页上找到了自己的文章,然而却不在自己的博客里。实在是诚惶诚恐。

转念想想没事多写写博客记录一下也是不错,所以就从移动硬盘里把博客源码翻了出来,从新部署后开始新的一章。

简介

最近正在写一个和代理服务器有关的项目,需要高并发的socket访问请求。因为自己写的最顺手的语言还是C语言和python。权衡了各种成本之后最后还是选择用python来实现。

本来项目一开始是用python2.7来写的。不过后来发现阬实在是不少。python2.7在signal还有异步方面确实做的不完善,进度很慢。后来发现python3.4开始就引入了asyncio库,并且将协程等一系列特性引入了python当中。所以趁着这个机会,果断的放弃了python2,转投了python3的怀抱。

虽然从python3.4开始就已经支持了协程的概念,不过在3.6版本上引入的await和async语法大大的方便了协程开发。这次这篇文章所使用的python版本也是3.6。

就几天的开发来看,python的协程因为是后开发的,还是有不少的问题的。不过用还是挺好用的。

其实最详细的介绍还是python官方的介绍文档。不过个人感觉python官方这种按线式的介绍顺序会对没有接触过这个模块的人感到一些困惑。而网上大部分的中文介绍也没有说重点的部分。所以这里就着重介绍一下asyncio关键的几个模块和地方。

Tasks和coroutines

这里首先介绍Tasks和coroutines。task其实就是一个可以被并发执行的任务。和操作系统中多任务部分的task有着差不多的含义。当把一个task放入一个eventloop中去之后,eventloop就会在合适的时候将程序的执行权力转交给这个task来执行。当然,协程并发执行和多线程或者多进程的访问有不同的地方。

首先,和线程不同,eventloop不像操作系统一样能够主动的收回程序的执行权限。它只能等待task主动的放弃程序的执行权限,然后eventloop才能将执行权限转移。那么task怎么放弃执行权限呢?有两种方法。

  1. task函数返回或者抛出异常。
  2. task调用await来等待一个future。

第一种方法很简单,task返回结束的时候自然就放弃了程序的执行权限。而其返回值或者是抛出的异常会保存在下来,程序可以通过一些方法从eventloop中得到,
而第二种方法就比较特殊了。这里首先介绍协程以及async和await语法。

协程其实就是一种特殊的函数,其脱胎于python的生成器语法。它和生成器语法有很多相似的地方,比如可以随时的将函数暂停,在保存函数栈帧的情况下将程序的执行流转义的其他的函数中。又比如调用协程不会直接执行他,而是返回协程的object。
声明协程的方法很简单。在函数的声明之前加async关键字(3.6之后)。或者是使用@asyncio.coroutine装饰函数(3.6之前)。

那一个协程能够做什么事情呢?除了一般函数能做的各种事情之外,还能做一些特别的事情。这里引用官方文档上的一段话。

  1. 使用await语法等待一个future完成。
  2. 使用await语法调用另外一个协程。
  3. 返回
  4. 抛出异常

后面两个很简单。前两个要好好说明一下。首先是调用另外一个协程,这个很简单,其实用法调用普通函数差不多。但是为什么要单独拿出来呢?因为协程是无法被直接调用的,一个协程的调用方式只有在另外一个协程里使用await调用,或者是将协程包裹成task放入eventloop中执行。

而future完成就比较玄妙了,future其实是task的基类。future其实就是对任务的一个抽象,一个future往往和一个任务相关,协程可以使用await来等待future的完成。当协程等待一个future完成的时候,他会放弃执行权力。eventloop会调用其他的task来执行。这也是之前所说task放弃程序执行的其中一种方法。

最后再介绍一个future,除了task这种future之外。自己也可以调用eventloop类中的Future方法来创建一个新的future。当然这个时候future何时完成就由自己来控制。调用future的cancelset_resultset_exception其中之一标示future完成或者出现异常。这样通过await等待future完成的协程就有机会被执行到。

Event loops

然后就是刚才一直在提及的event loops了。Event loops是asyncio模块的核心,在并发执行的过程中,负责监听事件的发生并且调度具体的协程。对外的主要接口是AbstractEventLoop类。从名字就可以看出来,这个类其实是个接口,具体实现的类根据平台并不相同,不过大部分情况下python都会根据平台选择适合的类。所以不用特别关心。

event loop的主要作用就是创建并执行task,而且因为asyncio主要还是为了网络io而准备的。所有eventloop中根据实现选择一种io多路转接的方法,并且会提供统一的api接口来创建和网络io有关的协程。有关这部分的内容比较容易理解,可以直接查看官方的api文档。

最后

其实asyncio的用法还是挺简单的,除了有关协程的调度有关的future和coroutine会比较容易让人迷惑一点,所以这里做了一些额外的介绍。就个人体验来说,如果用asyncio来做并发网络io的开发来说是非常不错的。但是如果想用它去实现对其他慢速任务的异步就会比较困难了。所以只能说是一个不错的网络模块。要是python的协程能再扩展一下就不错了。

当然,如果去深究协程的原理,会发现协程其实就是通过python3.3之后引入的python yield语法实现的。要是之后有时间的话,我会另外写一篇博客来大致解释一下python的异步语法以及能用它做些什么。