creeper项目规划

creeper的协程实现

  1. task 用来操作future
  2. future 协程系统的最小执行单元,切割具体的执行程序
  3. crawer 通过yield进行切割,分配给future
  4. loop 主循环,在循环内监听socket的事件,并执行注册的回调

不过感觉它这个系统耦合好严重啊,有什么化繁为简的方法么

画图分析了一下,发现协程执行过程当中future本身的创建和消化都是在crawer当中的,task所做的一是future的初始化,二是为future增加回调,三是启动程序

在这里将future似乎理解为尚未执行的操作会比较合适,那么crawer作为一个实际的执行者,来生成这个待执行的操作还是比较合适的,其代表的其实是从下一个yield到下下一个yield所要执行的过程,这个future其实并没有产生出来,其实回调函数就是在对这个future做操作,提供这个future的结果,但是future有了结果以后如何继续执行下去呢?这就需要设置结果这个操作自带一个控制future继续执行的操作,那么如何自带呢?将future生产出来给task,让task给你注册一下就完事了

因此标准的流程是 开始一个阶段的任务,对应一个future,在任务过程当中生成代表下一阶段任务的future,任务结束产出一个这个future(相当于告诉外界我下面要做什么),外界对这个future预处理一下,通常是注册函数(因为还没有值呢,能做什么其他处理),然后就是等待任务完成的回调。事件触发后相当于任务已经完成,回调产生future的结果,设置结果的时候通过触发回调函数来开始下一阶段的任务,开始任务的时候要把上一阶段的任务结果传进去,虽然crawer内部也可以获取。

这样的话task的作用相当于就是可以将整个协程跑起来,而future就是一个作用中间件,用来在控制程序和执行者之间传递信息。

这样来看,future和三方关联,task(控制程序)、外部系统(实际任务的执行者)、crawer(执行器),使用future的好处是外部系统回调的时候只需要返回一个值就行了,无需关心业务逻辑

不过作者还列举了很多好处,以我现在的水平还看不懂

协程的简单练习03

对于02的重构

有一个疑问:

注解

只是增加了iter()方法的实现。如果不把Future改成iterable也是可以的,还是用原来的yield f即可。那为什么需要改进呢?

首先,我们是在基于生成器做协程,而生成器还得是生成器,如果继续混用yield和yield from 做协程,代码可读性和可理解性都不好。其次,如果不改,协程内还得关心它等待的对象是否可被yield,如果协程里还想继续返回协程怎么办?如果想调用普通函数动态生成一个Future对象再返回怎么办?

所以,在Python 3.3 引入yield from新语法之后,就不再推荐用yield去做协程。全都使用yield from由于其双向通道的功能,可以让我们在协程间随心所欲地传递数据。

遇到一个坑,直接import loop进来发现无限循环,原来loop用的是02模块的全局变量left_tasks,而03模块中改变的是本模块的全局变量left_tasks

协程的简单练习02

跟着 深入理解异步编程 制作了一个基于协程的爬虫

这个过程当中的具体细节都写在代码的注释里了,附 代码文件

总结几个点:

  1. 这里面的逻辑好像是一个协程执行片段就是一个future,然后通过多个task去控制这些future,task同一时间所控制的future永远只有一个,每一个task对应一个爬虫任务,相当于这个task是一个控制器,协程运行结束都会返回一个新的future给他,相当于后续任务,而它就在返回的新future之上注册回调函数,这些回调函数是在新future完成之后就直接会被调用的,而回调函数的作用一般会包含继续运行协程并获取下一阶段的future,这两者是捆绑在一起的,所以一般来说协程总是会yield一个future,这也就解释了yield f的必要性,但还需理解得更加透彻
  2. 现在想来回调模式的协程编程和我原来想象的不太一样,原来以为主循环是直接去查看各个任务的状态,系统调用回调函数去改变状态。想想也不太可能,让系统来调用你的python函数听起来就很奇怪,而且你的程序已经在跑了别人还能调用?这应该是我之前对事件循环的理解错误,真正的事件循环应该是查询系统暴露出来的接口,看有没有事件发生,有发生主循环则调用对应的回调。
  3. 输出日志会降低速度,可能的原因是输出日志要进行文件I/O,不过输出日志不应该是多线程的么?从输出日志的时间在打印最终时间结果之后可以看出端倪,不过尚未确认

协程的简单练习01

使用协程制作了一个命令行的图形下载器,做完之后感觉对于协程的理解更深了,附 代码文件

总结以下几点:

  1. 使用协程很重要的一点是任务是可切分的,如果一个任务不可切分,那切成就无法并发了,因为毕竟只要一个线程
  2. 我自己感觉协程的运用有两种方式,一种是单纯的并发, 另外一种是异步回到模式,前者的话就是把多个任务进行拆分,然后不断地跑,跑完的那个返回一个状态给主循环。后者就是有一个任务队列,子任务会做搞一个耗时的任务,比如网络请求,发完之后直接返回主循环,请求来了之后调用该协程(该协程可能会提供一个回调函数什么的)调用完成之后协程中存储的状态发生改变,主循环只要反复去检查状态就可以了,一旦有状态发生改变就执行下一个任务
  3. 这条说明了我之前对于异步的理解是正确的

注解

所以,一旦采取异步编程,每个异步调用必须“足够小”,不能耗时太久。如何拆分异步任务成了难题。 程序下一步行为往往依赖上一步执行结果,如何知晓上次异步调用已完成并获取结果? 回调(Callback)成了必然选择。那又需要面临“回调地狱”的折磨。 同步代码改为异步代码,必然破坏代码结构。 解决问题的逻辑也要转变,不再是一条路走到黑,需要精心安排异步任务。

version 0.0.1

  1. 多线程、多进程爬虫,使用concurrent.future
  2. 协程爬虫,使用asyncio
  3. 都用最简单的例子来做,requests库足矣

version 0.0.1 项目总结