网络模型有不少中,为了实现高并发也有不少方案,多线程,多进程。不管多线程和多进程,IO的调度更多取决于系统,而协程的方式,调度来自用户,用户能够在函数中yield一个状态。使用协程能够实现高效的并发任务。Python的在3.4中引入了协程的概念,但是这个仍是以生成器对象为基础,3.5则肯定了协程的语法。下面将简单介绍asyncio的使用。实现协程的不只仅是asyncio,tornado和gevent都实现了相似的功能。python
上述的概念单独拎出来都很差懂,比较他们之间是相互联系,一块儿工做。下面看例子,再回溯上述概念,更利于理解。git
定义一个协程很简单,使用async关键字,就像定义普通函数同样:程序员
经过async关键字定义一个协程(coroutine),协程也是一种对象。协程不能直接运行,须要把协程加入到事件循环(loop),由后者在适当的时候调用协程。asyncio.get_event_loop
方法能够建立一个事件循环,而后使用run_until_complete
将协程注册到事件循环,并启动事件循环。由于本例只有一个协程,因而能够看见以下输出:github
Waiting: 2
TIME:
协程对象不能直接运行,在注册事件循环的时候,实际上是run_until_complete方法将协程包装成为了一个任务(task)对象。所谓task对象是Future类的子类。保存了协程运行后的状态,用于将来获取协程的结果。redis
能够看到输出结果为:编程
建立task后,task在加入事件循环以前是pending状态,由于do_some_work中没有耗时的阻塞操做,task很快就执行完毕了。后面打印的finished状态。网络
asyncio.ensure_future(coroutine) 和 loop.create_task(coroutine)均可以建立一个task,run_until_complete的参数是一个futrue对象。当传入一个协程,其内部会自动封装成task,task是Future的子类。isinstance(task, asyncio.Future)
将会输出True。多线程
绑定回调,在task执行完毕的时候能够获取执行的结果,回调的最后一个参数是future对象,经过该对象能够获取协程返回值。若是回调须要多个参数,能够经过偏函数导入。并发
def callback(t, future): print('Callback:', t, future.result()) task.add_done_callback(functools.partial(callback, 2))
回调一直是不少异步编程的恶梦,程序员更喜欢使用同步的编写方式写异步代码,以免回调的恶梦。回调中咱们使用了future对象的result方法。前面不绑定回调的例子中,咱们能够看到task有fiinished状态。在那个时候,能够直接读取task的result方法。app
能够看到输出的结果:
使用async能够定义协程对象,使用await能够针对耗时的操做进行挂起,就像生成器里的yield同样,函数让出控制权。协程遇到await,事件循环将会挂起该协程,执行别的协程,直到其余的协程也挂起或者执行完毕,再进行下一个协程的执行。
耗时的操做通常是一些IO操做,例如网络请求,文件读取等。咱们使用asyncio.sleep函数来模拟IO操做。协程的目的也是让这些IO操做异步化。
在 sleep的时候,使用await让出控制权。即当遇到阻塞调用的函数的时候,使用await方法将协程的控制权让出,以便loop调用其余的协程。如今咱们的例子就用耗时的阻塞操做了。
并发和并行一直是容易混淆的概念。并发一般指有多个任务须要同时进行,并行则是同一时刻有多个任务执行。用上课来举例就是,并发状况下是一个老师在同一时间段辅助不一样的人功课。并行则是好几个老师分别同时辅助多个学生功课。简而言之就是一我的同时吃三个馒头仍是三我的同时分别吃一个的状况,吃一个馒头算一个任务。
asyncio实现并发,就须要多个协程来完成任务,每当有任务阻塞的时候就await,而后其余协程继续工做。建立多个协程的列表,而后将这些协程注册到事件循环中。
结果以下
总时间为4s左右。4s的阻塞时间,足够前面两个协程执行完毕。若是是同步顺序的任务,那么至少须要7s。此时咱们使用了aysncio实现了并发。asyncio.wait(tasks) 也可使用 asyncio.gather(*tasks) ,前者接受一个task列表,后者接收一堆task。
使用async能够定义协程,协程用于耗时的io操做,咱们也能够封装更多的io操做过程,这样就实现了嵌套的协程,即一个协程中await了另一个协程,如此链接起来。
若是使用的是 asyncio.gather建立协程对象,那么await的返回值就是协程运行的结果。
不在main协程函数里处理结果,直接返回await的内容,那么最外层的run_until_complete将会返回main协程的结果。
或者返回使用asyncio.wait方式挂起协程。
也可使用asyncio的as_completed方法
因而可知,协程的调用和组合十分灵活,尤为是对于结果的处理,如何返回,如何挂起,须要逐渐积累经验和前瞻的设计。
上面见识了协程的几种经常使用的用法,都是协程围绕着事件循环进行的操做。future对象有几个状态:
建立future的时候,task为pending,事件循环调用执行的时候固然就是running,调用完毕天然就是done,若是须要中止事件循环,就须要先把task取消。可使用asyncio.Task获取事件循环的task
启动事件循环以后,立刻ctrl+c,会触发run_until_complete的执行异常 KeyBorardInterrupt。而后经过循环asyncio.Task取消future。能够看到输出以下:
True表示cannel成功,loop stop以后还须要再次开启事件循环,最后在close,否则还会抛出异常:
循环task,逐个cancel是一种方案,但是正如上面咱们把task的列表封装在main函数中,main函数外进行事件循环的调用。这个时候,main至关于最外出的一个task,那么处理包装的main函数便可。